手寫一個bind函式(大概)

conqorld發表於2019-04-26

昨天搜了下bind怎麼實現,大家都在說bind的特徵啊啥的,沒搜到,今天有人問起來,我反問他要咋搜,他和我說柯里化。柯里化!!!它的最常用的表現形式不就是傳說中的閉包的其中一種嘛!!!

I think! I can do it!!!我覺得我可以嘗試著實現一下。

apply的實現

首先我們來回想this指向的幾種可能性:

  1. 當函式作為一個物件的屬性被呼叫時,‘.’ 前面是誰this就指向誰。如:obj.run()、obj.a.run(),run函式裡的this就分別指向obj和a。
  2. 當給dom元素繫結事件時,方法裡的this指向繫結的dom元素。如:app.onclick = function a(){},a裡的this這時候指向app。
  3. 建構函式裡的this指向它的例項。如:function A(){this.a = 1}; let b = new A(),這時A中的this就指向b。
  4. 以上都不是,this指向window,嚴格模式指向undefind。

想想我們可以使用可能性1,先來寫個aplly方法:

先寫個實驗用具:

function sum(a, b) {
    return this.a + this.b
}
let obj1 = {
    a: 11,
    b: 7
}
let obj2 = {
    a: 1,
    b: 1,
}
複製程式碼

再寫個初版仿生myApply:

function myApply(that, arg){ //接受that引數引數,作為未來this
    that = JSON.parse(JSON.stringify(that)) //給做個深拷貝,不影響原來的物件
    that.Fn = this  //給物件設定屬性Fn,讓它等於我們前面呼叫myApply的sum
    return that.Fn(...arg)  //用屬性呼叫的方式執行函式,讓this指向傳入的that,放入引數
}
複製程式碼

但這麼寫有個問題,我們希望在sum裡面對this進行操作的時候,作為this源的物件也跟著改動,所以我們那麼寫:

function myApply(that, arg = []){
    that = that!== null && typeof that === 'object'?that: {} //在this不為object時的處理
    that.Fn = this
    const result = that.Fn(...arg)
    delete that.Fn //使用完之後對that下的Fn進行刪除
    return result
}
複製程式碼

完成後我們將myApply放到Function的原型上並測試看看它:

Function.prototype.myApply = myApply

console.log(sum1.myApply(obj1)) //18
console.log(sum2.myApply(null, [2, 3])) //5
複製程式碼

到這裡apply方法就算寫完了。

bind的實現

柯里化(curry):只傳遞給函式一部分引數來呼叫它,讓它返回一個函式去處理剩下的引數。

接下來我們要用到柯里化形式的閉包來儲存下bing函式呼叫時的this和傳進來的引數,以便在返回函式中用到它們:

function myBind(that, ...arg) { //建立時儲存bind中傳進來的arg
    const _Fn = this    //儲存sum函式
    return function () {
        const args = [...arg, ...arguments]   //合併兩次傳遞進來的引數
        return _Fn.myApply(that, args)  //呼叫sum函式的myApply方法
    }
}
複製程式碼

和前面一樣,將myBind放到Function的原型上,並測試它:

Function.prototype.myBind = myBind

const sum3 = sum1.myBind(obj1)
console.log(sum3()) //18
const sum4 = sum1.myBind(obj1).myBind(obj2)
console.log(sum4()) //18

const sum5 = sum2.myBind(obj1, 5)
console.log(sum5(6)) //11
複製程式碼

參考

相關文章