前端面試手寫篇
手寫篇
1. 手寫 instenceof
原生的
instanceof
console.log([] instanceof Array) // true
console.log('' instanceof Array) // false
手寫myInstanceof
:
function myInstanceof(left,right){ let proto = left.__proto__ let prototype = right.prototype while(true){ if(proto === null)return false if(proto === prototype)return true proto = proto.__proto__ } } console.log(myInstanceof([],Array))// true console.log(myInstanceof('',Array))// false
實現原理:
通過不斷的沿著原型鏈查詢,如果找到頂端了即:
proto === null
,那麼就說明沒有找到,返回false,說明left
不是right
建構函式的例項如果找到隱式原型
proto
等於建構函式的原型prototype
,那麼說明left
是right
建構函式的例項,返回true其它情況就是不斷的改變
proto
,以便可以不斷的往上查詢
2. 手寫 flat
原生示例:
const arr1 = [1, 2, [3, 4]]; arr1.flat(); // [1, 2, 3, 4] const arr2 = [1, 2, [3, 4, [5, 6]]]; arr2.flat(); // [1, 2, 3, 4, [5, 6]] const arr3 = [1, 2, [3, 4, [5, 6]]]; arr3.flat(2); // [1, 2, 3, 4, 5, 6] const arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]]; arr4.flat(Infinity); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
手寫flatDeep:
function flatDeep( arr, dep=1 ){ let ret = [] for(let i=0;i<arr.length;i++){ if(Array.isArray(arr[i])){ dep>0 ? (ret = ret.concat(flatter(arr[i],dep-1))):(ret.push(arr[i])) }else{ ret.push(arr[i]) } } return ret }
實現原理:
第一個引數是陣列,第二個是降維層級,
用for迴圈遍歷這個陣列,檢測每一項
如果這項是不是陣列則直接新增到
ret
結果數組裡面否則根據降維層級判斷,預設是降一維層級,當遞迴降維不滿足
ret>0
,說明已經達到dep降維層數了,其它情況即ret.push(arr[i])
3. 手寫 call
Function.prototype.myCall = function(context){
context =(context === null || context === undefined) ? window : context
context.fn = this// 其實就等價於 obj.fn = function say(){} 當指向 context.fn 時,say裡面的this 指向obj [關鍵]
//obj 此時變成 var obj = {name:'innerName',fn:function say(){console.log(this.name)}}
let args = [...arguments].slice(1) //擷取第二個開始的所有引數
let result= context.fn(...args)//把執行的結果賦予result變數
delete context.fn //刪除執行上下文上的屬性 (還原)由var obj = {name:'innerName',fn:function say(){console.log(this.name)}}刪除fn
return result
}
var name = 'outerName'
var obj = {
name:'innerName'
}
function say(){
console.log(this.name)
}
say()//outerName 等價於 window.say this指向window
say.myCall(obj)//innerName
實現原理:
函式的原型方法call 第一個引數是傳入的執行上下文,後面傳入的都是引數,以逗號隔開
當傳入的是null或undefined是執行上下文是指向
window
,否使為傳入的物件,然後再傳入的物件身上新增fn
屬性並把函式例項say函式賦值給fn,此時變成
var obj = {name:'innerName',fn:function say(){console.log(this.name)}}
此時context
就是obj物件啦,所有你執行context.fn(...args)
其實就是
obj.fn(...args)
,fn
其值是function say(){ console.log(this.name) }
,所以這個this
就變成obj
物件了然後就是結果賦值,物件還原
返回結果
4. 手寫 apply
Function.prototype.myApply = function(context){
context =(context === null || context === undefined) ? window : context
let result
context.fn = this
result = arguments[1] ? context.fn(...arguments[1]) : context.fn()
delete context.fn
return result
}
同
myCall
實現原理大致相同,不同的是由於call
和apply
的傳參方式不一樣,我們需要額外的對第二個引數做判斷,
apply
受參形式是陣列,且再第二個引數位置,一:如果第二個引數存在,執行的時候就把第二個引數(陣列形式)用擴充套件運算子打散後傳入執行
二:如果第二個引數不存在,執行執行
其它就於
call
的實現一樣
5. 手寫 bind
Function.prototype.myBind = function(context){
context =(context === null || context === undefined) ? window : context
let o = Object.create(context)
o.fn = this
let args = [...arguments].slice(1)
let fn= function(){
o.fn(...args)
}
return fn
}
bind 的手寫實現,與其它兩個區別是返回一個函式,並沒返回函式執行的結果,並且受參形式不受限制
實現原理:
通過
Object.create
方法建立一個新物件,使用現有的物件來提供新建立的物件的__proto__
,通過 中介物件o
來實現,來達到不影響傳入的物件
6. 手寫 new
new 一個函式的時候,會生成一個例項,該例項的隱式原型
__proto__
===該函式的prototype
原型物件在建構函式中this指向當前例項
最後再將例項物件返回
function myNew(func){
//第一步 將函式的 prototype 指向 o 物件的__proto__
let o = Object.create(func.prototype)
//第二步 通過call改變 this的指向,使之指向 o
let ret = func.call(o)
//第三步 如果建構函式裡面有返回物件,則返回這個物件,沒有則返回 o 物件
return typeof ret === 'object' ? ret : o
}
檢測:
function M(){}
let m = myNew(M); // 等價於 new M 這裡只是模擬
console.log(m instanceof M); // instanceof 檢測例項
console.log(m instanceof Object);
console.log(m.__proto__.constructor === M);