【javascript】關於bind的實現

前言

  • 聽說bind有點特別啊,一開始初學不知道,以爲那麼簡單的東西就綁成函數等待執行唄,後來發現這玩意實際還是比較複雜的。

mdn上Polyfill

  • 先看mdn上Polyfill
// Does not work with `new funcA.bind(thisArg, args)`
if (!Function.prototype.bind) (function(){
  var slice = Array.prototype.slice;
  Function.prototype.bind = function() {
    var thatFunc = this, thatArg = arguments[0];
    var args = slice.call(arguments, 1);
    if (typeof thatFunc !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - ' +
             'what is trying to be bound is not callable');
    }
    return function(){
      var funcArgs = args.concat(slice.call(arguments))
      return thatFunc.apply(thatArg, funcArgs);
    };
  };
})();
  • 前面有個註釋,這個不支持在用new來生成綁定函數。也就是說,原版的bind可以new的。
  • 當然原版還有很多特別的地方。寫幾個小例子測試下原版。

原版測試

  • 簡單情況:
function aaa() { }
ccc = aaa.bind('ddd')
console.log(ccc.__proto__)
console.log(ccc.constructor)
console.log(ccc.prototype)
  • 這個就是隻有一個函數然後直接bind情況。這3個打印分別是Function.prototype,Function,undefined。
  • 就是這個undefined表示了函數生成的bind函數,無prototype。
  • 這好像暗示我們bind內部用箭頭函數實現,但是,箭頭函數不能new啊。
  • 使用傳參形式結合new看看效果:
function aaa(x,y) {
    this.x=x
    this.y=y
}
aaa.prototype.getXY=function(){
    return this.x+this.y
}
let bindfunc = aaa.bind(null,11)
let ins = new bindfunc(33)
console.log(ins.getXY())//表示實例能拿原函數的prototype
console.log(ins instanceof bindfunc)//true
console.log(ins instanceof aaa)//true
console.log(ins.constructor)//aaa
console.log(bindfunc.constructor)//同第一種情況
console.log(bindfunc.__proto__)//同上
console.log(bindfunc.prototype)//同上
  • 通過console其實可以推斷,bind所返回的函數的prototype,應該是能通過原型鏈找到原函數的prototype的。而且如果new的話,this不是指向綁定this,而是指向實例本身(這個就有點跟bind設計的意思衝突,用bind想改變this,結果new的時候this又不改變)。
  • 正常的函數,new出來實例的constructor會指向這個函數。所以正常來說,這個Ins的constructor應該指向bindfunc纔對。所以bindfunc的prototype被調換過了。
  • 看一下mdn推薦的大神寫的bind打印結果也和真正的bind不一樣。大神寫的bind地址
  • 用上面那個例子測試大神寫的這個bind,會發現打印結果:
44
true
true
[Function: aaa]
[Function: Function]
[Function] { mybind: [Function: mybind] }
aaa {}
  • 大神除了undefined問題,其他基本上都跟原版打印的一樣。

實現bind

  • 對於這種問題,最好就是畫個圖,然後照着圖連就行了。這裏我畫了個圖:
    在這裏插入圖片描述
  • 可以發現,如果用Js實現bind,undefined問題是無法避免的,因爲你能new,而且必須讓實例找到原函數上,就得設置bindfunc的prototype。不然你new出來的實例找不到路。雖然js可以通過給bindfunc設置代理,讓其prototype的打印值爲undefined,但是這樣你返回的函數就是代理對象,而不是原來那個綁定函數。
  • 我們可以通過instanceof 判斷是不是被new調用,如果是new 調用,那麼外面那個this是原函數,裏面那個this是new出來的對象。
  • 最後可以把前面大神寫的簡略點,改成這樣:
Function.prototype.mybind = function (context, ...args) {
    let fn = this
    if (typeof fn !== 'function') {
        throw 'error'
    }
    let fbound = function (...innerArgs) {
        return fn.apply(
            this instanceof fn ? this : context, [...args, ...innerArgs]
        )
    }
    fbound.prototype = Object.create(fn.prototype)
    return fbound
}


function aaa(x, y) {
    this.x = x
    this.y = y
}
aaa.prototype.getXY = function () {
    return this.x + this.y
}
let bindfunc = aaa.mybind(null, 11)
let ins = new bindfunc(33)
console.log(ins.getXY())//表示實例能拿原函數的prototype
console.log(ins instanceof bindfunc)//true
console.log(ins instanceof aaa)//true
console.log(ins.constructor)
console.log(bindfunc.constructor)
console.log(bindfunc.__proto__)
console.log(bindfunc.prototype)
  • 這樣基本上就差不多了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章