前端面試js算法題


2019前端面試系列——JS高頻手寫代碼題
目錄

實現 new 方法
實現 Promise
實現一個 call 函數
實現一個 apply 函數
實現一個 bind 函數
淺拷貝、深拷貝的實現
實現一個節流函數
實現一個防抖函數
instanceof 的原理
柯里化函數的實現
Object.create 的基本實現原理
實現一個基本的 Event Bus
實現一個雙向數據綁定
實現一個簡單路由
實現懶加載
rem 基本設置
手寫實現 AJAX

實現 new 方法
復制
/*

  • 1.創建一個空對象

  • 2.鏈接到原型

  • 3.綁定this值

  • 4.返回新對象
    */
    // 第一種實現
    function createNew() {
    let obj = {} // 1.創建一個空對象

    let constructor = [].shift.call(arguments)
    // let [constructor,...args] = [...arguments]

    obj.proto = constructor.prototype // 2.鏈接到原型

    let result = constructor.apply(obj, arguments) // 3.綁定this值
    // let result = constructor.apply(obj, args)

    return typeof result === 'object' ? result : obj // 4.返回新對象
    }

function People(name,age) {
this.name = name
this.age = age
}

let peo = createNew(People,'Bob',22)
console.log(peo.name)
console.log(peo.age)

實現 Promise

// 未添加異步處理等其他邊界情況
// ①自動執行函數,②三個狀態,③then
class Promise {
constructor (fn) {
// 三個狀態
this.state = 'pending'
this.value = undefined
this.reason = undefined
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
}
}
let reject = value => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = value
}
}
// 自動執行函數
try {
fn(resolve, reject)
} catch (e) {
reject(e)
}
}
// then
then(onFulfilled, onRejected) {
switch (this.state) {
case 'fulfilled':
onFulfilled(this.value)
break
case 'rejected':
onRejected(this.value)
break
default:
}
}
}

實現一個 call 函數

// 思路:將要改變this指向的方法掛到目標this上執行並返回
Function.prototype.mycall = function (context) {
if (typeof this !== 'function') {
throw new TypeError('not funciton')
}
context = context || window
context.fn = this
let arg = [...arguments].slice(1)
let result = context.fn(...arg)
delete context.fn
return result
}

實現一個 apply 函數

// 思路:將要改變this指向的方法掛到目標this上執行並返回
Function.prototype.myapply = function (context) {
if (typeof this !== 'function') {
throw new TypeError('not funciton')
}
context = context || window
context.fn = this
let result
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}

實現一個 bind 函數

// 思路:類似call,但返回的是函數
Function.prototype.mybind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
let _this = this
let arg = [...arguments].slice(1)
return function F() {
// 處理函數使用new的情況
if (this instanceof F) {
return new _this(...arg, ...arguments)
} else {
return _this.apply(context, arg.concat(...arguments))
}
}
}
更多介紹:bind方法的實現

淺拷貝、深拷貝的實現
淺拷貝:

// 1. ...實現
let copy1 = {...{x:1}}

// 2. Object.assign實現
let copy2 = Object.assign({}, {x:1})
深拷貝:

// 1. JOSN.stringify()/JSON.parse()
// 缺點:拷貝對象包含 正則表達式,函數,或者undefined等值會失敗
let obj = {a: 1, b: {x: 3}}
JSON.parse(JSON.stringify(obj))

// 2. 遞歸拷貝
function deepClone(obj) {
let copy = obj instanceof Array ? [] : {}
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
copy[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
}
}
return copy
}

實現一個節流函數

// 思路:在規定時間內只觸發一次
function throttle (fn, delay) {
// 利用閉包保存時間
let prev = Date.now()
return function () {
let context = this
let arg = arguments
let now = Date.now()
if (now - prev >= delay) {
fn.apply(context, arg)
prev = Date.now()
}
}
}

function fn () {
console.log('節流')
}
addEventListener('scroll', throttle(fn, 1000))

實現一個防抖函數

// 思路:在規定時間內未觸發第二次,則執行
function debounce (fn, delay) {
// 利用閉包保存定時器
let timer = null
return function () {
let context = this
let arg = arguments
// 在規定時間內再次觸發會先清除定時器后再重設定時器
clearTimeout(timer)
timer = setTimeout(function () {
fn.apply(context, arg)
}, delay)
}
}

function fn () {
console.log('防抖')
}
addEventListener('scroll', debounce(fn, 1000))

instanceof 的原理

// 思路:右邊變量的原型存在於左邊變量的原型鏈上
function instanceOf(left, right) {
let leftValue = left.proto
let rightValue = right.prototype
while (true) {
if (leftValue === null) {
return false
}
if (leftValue === rightValue) {
return true
}
leftValue = leftValue.proto
}
}

柯里化函數的實現
柯里化函數的定義:將多參數的函數轉換成單參數的形式。
柯里化函數實現的原理:利用閉包原理在執行可以形成一個不銷毀的作用域,然后把需要預先處理的內容都儲存在這個不銷毀的作用域中,並且返回一個最少參數函數。

第一種:固定傳入參數,參數夠了才執行

/**

  • 實現要點:柯里化函數接收到足夠參數后,就會執行原函數,那么我們如何去確定何時達到足夠的參數呢?
  • 柯里化函數需要記住你已經給過他的參數,如果沒給的話,則默認為一個空數組。
  • 接下來每次調用的時候,需要檢查參數是否給夠,如果夠了,則執行fn,沒有的話則返回一個新的 curry 函數,將現有的參數塞給他。

*/
// 待柯里化處理的函數
let sum = (a, b, c, d) => {
return a + b + c + d
}

// 柯里化函數,返回一個被處理過的函數
let curry = (fn, ...arr) => { // arr 記錄已有參數
return (...args) => { // args 接收新參數
if (fn.length <= (...arr,...args)) { // 參數夠時,觸發執行
return fn(...arr, ...args)
} else { // 繼續添加參數
return curry(fn, [...arr, ...args])
}
}
}

var sumPlus = curry(sum)
sumPlus(1)(2)(3)(4)
sumPlus(1, 2)(3)(4)
sumPlus(1, 2, 3)(4)
第二種:不固定傳入參數,隨時執行

/**

  • 當然了,柯里化函數的主要作用還是延遲執行,執行的觸發條件不一定是參數個數相等,也可以是其他的條件。
  • 例如參數個為0的情況,那么我們需要對上面curry函數稍微做修改
    */
    // 待柯里化處理的函數
    let sum = arr => {
    return arr.reduce((a, b) => {
    return a + b
    })
    }

let curry = (fn, ...arr) => { // arr 記錄已有參數
return (...args) => { // args 接收新參數
if (args.length === 0) { // 參數為空時,觸發執行
return fn(...arr, ...args)
} else { // 繼續添加參數
return curry(fn, ...arr, ...args)
}
}
}

var sumPlus = curry(sum)
sumPlus(1)(2)(3)(4)()
sumPlus(1, 2)(3)(4)()
sumPlus(1, 2, 3)(4)()
參考鏈接:js如何用一句代碼實現函數的柯里化(ES6)

Object.create 的基本實現原理

// 思路:將傳入的對象作為原型
function create(obj) {
function F() {}
F.prototype = obj
return new F()
}

實現一個基本的 Event Bus

// 組件通信,一個觸發與監聽的過程
class EventEmitter {
constructor () {
// 存儲事件
this.events = this.events || new Map()
}
// 監聽事件
addListener (type, fn) {
if (!this.events.get(type)) {
this.events.set(type, fn)
}
}
// 觸發事件
emit (type) {
let handle = this.events.get(type)
handle.apply(this, [...arguments].slice(1))
}
}

// 測試
let emitter = new EventEmitter()
// 監聽事件
emitter.addListener('ages', age => {
console.log(age)
})
// 觸發事件
emitter.emit('ages', 18) // 18
回到頂部
實現一個雙向數據綁定
復制
let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 數據劫持
Object.defineProperty(obj, 'text', {
configurable: true,
enumerable: true,
get() {
console.log('獲取數據了')
},
set(newVal) {
console.log('數據更新了')
input.value = newVal
span.innerHTML = newVal
}
})
// 輸入監聽
input.addEventListener('keyup', function(e) {
obj.text = e.target.value
})
詳細的實現見:這應該是最詳細的響應式系統講解了

實現一個簡單路由

// hash路由
class Route{
constructor(){
// 路由存儲對象
this.routes = {}
// 當前hash
this.currentHash = ''
// 綁定this,避免監聽時this指向改變
this.freshRoute = this.freshRoute.bind(this)
// 監聽
window.addEventListener('load', this.freshRoute, false)
window.addEventListener('hashchange', this.freshRoute, false)
}
// 存儲
storeRoute (path, cb) {
this.routes[path] = cb || function () {}
}
// 更新
freshRoute () {
this.currentHash = location.hash.slice(1) || '/'
this.routesthis.currentHash
}
}

實現懶加載

let imgs = document.querySelectorAll('img')
// 可視區高度
let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
function lazyLoad () {
// 滾動卷去的高度
let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
for (let i = 0; i < imgs.length; i ++) {
// 圖片在可視區冒出的高度
let x = clientHeight + scrollTop - imgs[i].offsetTop
// 圖片在可視區內
if (x > 0 && x < clientHeight+imgs[i].height) {
imgs[i].src = imgs[i].getAttribute('data')
}
}
}
// addEventListener('scroll', lazyLoad) or setInterval(lazyLoad, 1000)

rem 基本設置

// 提前執行,初始化 resize 事件不會執行
setRem()
// 原始配置
function setRem () {
let doc = document.documentElement
let width = doc.getBoundingClientRect().width
let rem = width / 75
doc.style.fontSize = rem + 'px'
}

// 監聽窗口變化
addEventListener("resize", setRem)

手寫實現 AJAX

// 1. 簡單流程

// 實例化
let xhr = new XMLHttpRequest()
// 初始化
xhr.open(method, url, async)
// 發送請求
xhr.send(data)
// 設置狀態變化回調處理請求結果
xhr.onreadystatechange = () => {
if (xhr.readyStatus === 4 && xhr.status === 200) {
console.log(xhr.responseText)
}
}

// 2. 基於promise實現

function ajax (options) {
// 請求地址
const url = options.url
// 請求方法
const method = options.method.toLocaleLowerCase() || 'get'
// 默認為異步true
const async = options.async
// 請求參數
const data = options.data
// 實例化
const xhr = new XMLHttpRequest()
// 請求超時
if (options.timeout && options.timeout > 0) {
xhr.timeout = options.timeout
}
// 返回一個Promise實例
return new Promise ((resolve, reject) => {
xhr.ontimeout = () => reject && reject('請求超時')
// 監聽狀態變化回調
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
// 200-300 之間表示請求成功,304資源未變,取緩存
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
resolve && resolve(xhr.responseText)
} else {
reject && reject()
}
}
}
// 錯誤回調
xhr.onerror = err => reject && reject(err)
let paramArr = []
let encodeData
// 處理請求參數
if (data instanceof Object) {
for (let key in data) {
// 參數拼接需要通過 encodeURIComponent 進行編碼
paramArr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
}
encodeData = paramArr.join('&')
}
// get請求拼接參數
if (method === 'get') {
// 檢測url中是否已存在 ? 及其位置
const index = url.indexOf('?')
if (index === -1) url += '?'
else if (index !== url.length -1) url += '&'
// 拼接url
url += encodeData
}
// 初始化
xhr.open(method, url, async)
// 發送請求
if (method === 'get') xhr.send(null)
else {
// post 方式需要設置請求頭
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded;charset=UTF-8')
xhr.send(encodeData)
}
})
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM