實現(xiàn) Function.prototype.bind 的 Polyfill

最近的面試中被問到了 bind 這個方法,并寫出其 Polyfill,感覺回答的不太好,在此重新整理一下。
Function.prototype.bind 是 ES5 引入的方法,會返回一個新函數(shù)并修改函數(shù)的 this 指向,舉個例子(摘自 MDN):

this.x = 9; 
let module = {
  x: 81,
  getX() {
    return this.x; 
  }
};

module.getX();  // 返回81,this指向module

let retrieveX = module.getX;
retrieveX();  // 返回9,this指向全局作用域

// 創(chuàng)建一個新函數(shù),將this綁定到module對象
let boundGetX = retrieveX.bind(module);
boundGetX();  // 返回81,this指向module

所以,可以容易得出:

Function.prototype.bind = function (thisArg) {
  var self = this;
  return function() {
    return self.apply(thisArg, arguments);
  }
}

注意到 bind 方法的定義如下:

fun.bind(thisArg[, arg1[, arg2[, ...]]])
參數(shù)
thisArg
當綁定函數(shù)被調(diào)用時,該參數(shù)會作為原函數(shù)運行時的this指向。當使用new操作符調(diào)用綁定函數(shù)時,該參數(shù)無效。
arg1, arg2, ...
當綁定函數(shù)被調(diào)用時,這些參數(shù)將置于實參之前傳遞給被綁定的方法。

因此,bind 除了可以綁定 this 指向外,還可以綁定調(diào)用參數(shù)。代碼修改如下:

Function.prototype.bind = function (thisArg) {
  // 借用 Array 的 slice 方法去掉第一個參數(shù) thisArg,剩下的是函數(shù)調(diào)用參數(shù)
  var bindArgs = Array.prototype.slice.call(arguments, 1);
  var self = this;
  return function() {
    return self.apply(thisArg, bindArgs.concat(arguments));
  }
}

此外,還需要考慮到綁定的函數(shù)可以是構(gòu)造函數(shù),然而返回的新函數(shù)不具有綁定函數(shù)的原型鏈,因而需要修復原型鏈。代碼修改如下:

Function.prototype.bind = function (thisArg) {
  // 借用 Array 的 slice 方法去掉第一個參數(shù) thisArg,剩下的是函數(shù)調(diào)用參數(shù)
  var bindArgs = Array.prototype.slice.call(arguments, 1);
  var self = this;
  var bindFunction = function() {
    return self.apply(thisArg, bindArgs.concat(arguments));
  }
  // 引入空函數(shù) F,避免原型鏈上引用類型屬性共享
  function F() {}
  F.prototype = this.prototype;
  bindFunction.prototype = new F();
  // 修復 constructor 屬性
  bindFunction.prototype.constructor = this;
  return bindFunction;
}

至此完成了 Function.prototype.bind 的 Polyfill。
附 MDN 提供的 Polyfill,完善了邊界和異常:

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== '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');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          return fToBind.apply(this instanceof fNOP
                 ? this
                 : oThis,
                 // 獲取調(diào)用時(fBound)的傳參,bind 返回的函數(shù)入?yún)⑼沁@么傳遞的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // 維護原型關(guān)系
    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    fBound.prototype = new fNOP();

    return fBound;
  };
}

仍然存在的問題:

  1. 這部分實現(xiàn)依賴于 Array.prototype.slice(),Array.prototype.concat()Function.prototype.call() 這些原生方法。
  2. 這部分實現(xiàn)創(chuàng)建的函數(shù)的實現(xiàn)并沒有 caller 以及會在 get,set 或者 deletion 上拋出 TypeError 錯誤的 arguments 屬性這兩個不可改變的“毒藥” 。(假如環(huán)境支持 Object.defineProperty,或者實現(xiàn)支持 __defineGetter____defineSetter__ 擴展)
  3. 這部分實現(xiàn)創(chuàng)建的函數(shù)有 prototype 屬性。(正確的綁定函數(shù)沒有的)
  4. 這部分實現(xiàn)創(chuàng)建的綁定函數(shù)所有的 length 屬性并不是同 ECMA-262 標準一致的:它的 length 是 0,而在實際的實現(xiàn)中根據(jù)目標函數(shù)的 length 和預先指定的參數(shù)個數(shù)可能會返回非零的 length。

Reference

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容