最近的面試中被問到了 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
當(dāng)綁定函數(shù)被調(diào)用時序厉,該參數(shù)會作為原函數(shù)運行時的this
指向锐膜。當(dāng)使用new
操作符調(diào)用綁定函數(shù)時,該參數(shù)無效脂矫。
arg1, arg2, ...
當(dāng)綁定函數(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ù)的原型鏈恨搓,因而需要修復(fù)原型鏈院促。代碼修改如下:
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();
// 修復(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)));
};
// 維護(hù)原型關(guān)系
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
}
仍然存在的問題:
- 這部分實現(xiàn)依賴于
Array.prototype.slice()
弄抬,Array.prototype.concat()
,Function.prototype.call()
這些原生方法宪郊。 - 這部分實現(xiàn)創(chuàng)建的函數(shù)的實現(xiàn)并沒有
caller
以及會在get
掂恕,set
或者deletion
上拋出TypeError
錯誤的arguments
屬性這兩個不可改變的“毒藥” 。(假如環(huán)境支持Object.defineProperty
弛槐,或者實現(xiàn)支持__defineGetter__
和__defineSetter__
擴展) - 這部分實現(xiàn)創(chuàng)建的函數(shù)有
prototype
屬性懊亡。(正確的綁定函數(shù)沒有的) - 這部分實現(xiàn)創(chuàng)建的綁定函數(shù)所有的
length
屬性并不是同ECMA-262
標(biāo)準(zhǔn)一致的:它的length
是 0,而在實際的實現(xiàn)中根據(jù)目標(biāo)函數(shù)的length
和預(yù)先指定的參數(shù)個數(shù)可能會返回非零的length
乎串。
Reference
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind