bind() 方法的特點
- bind() 方法會創建一個新函數。
- 當這個新函數被調用時,bind() 的第一個參數將作爲它運行時的 this,
- 可以把除了第一個參數以外的其他參數都傳遞給下層的函數(這種技術稱爲“部分應用”,是“柯里化”的一種)注①
- 如果 bind() 返回的函數 作爲構造函數使用,bind 時指定的 this 值會失效,但傳入的參數有效。
- new 調用返回的實例能夠繼承 綁定函數的原型 中的值
注①:
- 來自《你不知道的JavaScript》
關於局部應用與柯里化,引用 mqyqingfeng:
- 柯里化是將一個多參數函數轉換成多個單參數函數,也就是將一個 n 元函數轉換成 n 個一元函數。
- 局部應用則是固定一個函數的一個或者多個參數,也就是將一個 n 元函數轉換成一個 n - x 元函數。
- 如果說兩者有什麼關係的話,引用 functional-programming-jargon 中的描述就是:Curried functions are automatically partially applied.
bind() 實現
方法一
Function.prototype.fakeBind = function(context) {
if (typeof this !== "function") {
throw new Error("Bind must be called on a function");
}
let self = this;
// 獲得第一個參數以外的其他參數
let args = Array.prototype.slice.call(arguments, 1);
let inner = function() {
// 獲得返回函數執行時被傳入的參數
let innerArgs = Array.prototype.slice.call(arguments);
// 1 new 情況下,this 指向實例。此時 bind 時指定的 this 值應予以失效;
// 2 實例 instanceof 構造函數 返回值 true、false,確定是否爲 new 調用;
// 3 匿名函數直接調用 this 指向全局對象。此時應予以修改 this 的綁定
return self.apply(
this instanceof inner ?
this :
context, args.concat(innerArgs)
);
};
// inner.prototype = this.prototype
// 按上面這麼寫的話,修改 返回函數原型對象(inner.prototype)的同時把 綁定函數的原型對象(this.prototype)也同時修改了。
// 用匿名函數做中轉,this.protptype 就安全了。畫個原型鏈的圖就清楚了。
//注②
let fNOP = function() {};
fNOP.prototype = this.prototype;
inner.prototype = new fNOP();
return inner;
};
注②
fNOP.prototype = this.prototype; 就是將 this.prototype 原型對象作爲 fNOP.prototype 的原型對象,也就是 this.prototype 和 fNOP.prototype 指向同一個對象。
像 var f = new fNOP(); 之後找原型鏈上的屬性,就是通過 f.proto,
因爲 f.proto == fNOP.prototype == this.prototype
就會去 this.prototype 上找屬性了。
方法二 ES6版
Function.prototype.fakeBindES6 = function(context, ...rest) {
if (typeof this !== "function") {
throw new Error("Bind must be called on a function");
}
var self = this;
return function inner(...args) {
if (this instanceof inner) {
// 當返回的內層函數作爲構造函數使用,bind 時綁定的 this 失效。
// 即此處直接執行綁定函數,而不使用 apply 對 this 進行綁定
return new self(...rest, ...args);
}
// 當作爲普通函數調用,this 指向傳入的對象
return self.apply(context, rest.concat(args));
};
};