bind
原文詳見: JavaScript深入之bind的模擬實現(xiàn)
一句話介紹 bind (來自于 MDN ):
bind() 方法會創(chuàng)建一個新函數(shù)。當這個新函數(shù)被調用時,bind() 的第一個參數(shù)將作為它運行時的 this,之后的一序列參數(shù)將會在傳遞的實參前傳入作為它的參數(shù)。
由此我們可以首先得出 bind 函數(shù)的兩個特點:
1.返回一個新函數(shù)
2.可以傳入?yún)?shù)
返回函數(shù)的模擬實現(xiàn)
從第一個特點開始,我們舉個例子:
var value = 2;
var foo = {
value: 1
}
function bar() {
console.log(this.value);
}
//返回了一個新函數(shù)
var bindFoo = bar.bind(foo);
bindFoo();
//1
關于指定 this 的指向,我們可以使用 call 或者 apply 實現(xiàn)。我們來寫第一版的代碼:
//第一版
Function.prototype.bind2 = function(context) {
var self = this;
return function() {
//之所以 return self.apply(context),
//是考慮到綁定函數(shù)可能是有返回值的
return self.apply(context);
}
}
var value = 2;
var foo = {
value: 1
}
function bar() {
console.log(this.value);
}
var bindFoo = bar.bind2(foo);
bindFoo();
//1
傳參的模擬實現(xiàn)
接下來看第二點,可以傳入?yún)?shù)。這個就有點讓人費解了,我在 bind 的時候,是否可以傳參呢?我在執(zhí)行 bind 返回的函數(shù)的時候,可不可以傳參呢?讓我們看個例子:
var foo = {
value: 1
};
function bar(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFoo = bar.bind(foo, 'daisy');
bindFoo('18');
// 1
// daisy
// 18
函數(shù)需要傳 name 和 age 兩個參數(shù),竟然還可以在 bind 的時候,只傳一個 name,在執(zhí)行返回的函數(shù)的時候,再傳另一個參數(shù) age!
這可咋辦?不急,我們用 arguments 進行處理:
//第二版
Function.prototype.bind2 = function(context) {
var self = this;
var args = Array.prototype.slice.call(arguments,1);
//執(zhí)行bind返回的函數(shù)的時候才會走到執(zhí)行function
return function() {
//這時候的arguments是指向bind返回函數(shù)傳入的參數(shù)
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(context, args.concat(bindArgs));
}
}
注意:Array.prototype.slice.call 改變this的指向指向arguments 并且slice能轉化arguments類數(shù)組為數(shù)組實現(xiàn)slice截取的作用,并返回一個新數(shù)組。還可以寫成[].slice.call(); 因為[]是Array的一個實例。
構造函數(shù)效果的模擬實現(xiàn)
bind 還有一個特點,就是
一個綁定函數(shù)也能使用new操作符創(chuàng)建對象:這種行為就像把原函數(shù)當成構造器。提供的 this 值被忽略,同時調用時的參數(shù)被提供給模擬函數(shù)。
也就是說當 bind 返回的函數(shù)作為構造函數(shù)的時候,bind 時指定的 this 值會失效,但傳入的參數(shù)依然生效。舉個例子:
var value = 2;
var foo = {
value: 1
};
function bar(name, age) {
this.habit = 'shopping';
console.log(this.value);
console.log(name);
console.log(age);
}
bar.prototype.friend = 'kevin';
var bindFoo = bar.bind(foo, 'daisy');
var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin
注意:盡管在全局和 foo 中都聲明了 value 值,最后依然返回了 undefind,說明綁定的 this 失效了,如果大家了解 new 的模擬實現(xiàn),就會知道這個時候的 this 已經(jīng)指向了 obj。
所以我們可以通過修改返回的函數(shù)的原型來實現(xiàn),讓我們寫一下:
//第三版
Function.prototype.bind2 = function(context) {
var self= this;
var args = [].slice.call(arguments,1);
var fBood = function() {
var newArgs = [].slice.call(arguments);
// 當作為構造函數(shù)時,this 指向實例,此時結果為 true,將綁定函數(shù)的 this 指向該實例,
//可以讓實例獲得來自綁定函數(shù)的值
// 以上面的是 demo 為例,如果改成 `this instanceof fBound ? null : context`,
//實例只是一個空對象,將 null 改成 this ,實例會具有 habit 屬性
// 當作為普通函數(shù)時,this 指向 window
//此時結果為 false,將綁定函數(shù)的 this 指向 context
return self.apply(this instanceof fBood? this: context,args.concat(newArgs));
}
// 修改返回函數(shù)的 prototype 為綁定函數(shù)的 prototype,
//實例就可以繼承綁定函數(shù)的原型中的值
fBood.prototype = this.prototype;
return fBood;
}
注意1:this instanceof fBound 用來檢測 構造函數(shù)的prototype屬性是否出現(xiàn)在某個實例對象的原型鏈上。
如果你用new 來調用一個bind過的函數(shù),這時候this instanceof fBood === true ,這時候就無視bind的效果(this失效),因此this該是什么就是什么。
注意2:fBood.prototype = this.prototype 為了讓fBood構造函數(shù)實例能夠繼承綁定函數(shù)的原型中的值
構造函數(shù)效果的優(yōu)化實現(xiàn)
但是在這個寫法中,我們直接將 fBound.prototype = this.prototype,我們直接修改 fBound.prototype 的時候,也會直接修改綁定函數(shù)的 prototype。
舉個例子:
Function.prototype.bind2 = function(context) {
var self = this;
var args = Array.prototype.slice.call(arguments,1);
var fBood = function() {
var newArgs = [].slice.call(arguments);
self.apply(this instanceof fBood? this: context, args.concat(newArgs));
}
fBood.prototype = this.prototype;
return fBood;
}
function bar() {}
var bindFoo = bar.bind(null);
bindFoo.prototype.value = 1;
console.log(bar.prototype.value); //1
(你會發(fā)現(xiàn)我們明明修改的是 bindFoo.prototype ,但是 bar.prototype 的值也被修改了,這就是因為 fBound.prototype = this.prototype導致的。)
//第四版
Function.prototype.bind2 = function(context) {
var self = this;
var args = [].slice.call(arguments, 1);
var fBood = function() {
var newArgs = [].slice.call(arguments);
self.apply(this instanceof fBood? this: context,args.concat(newArgs));
}
fNOP.peototype = this.prototype;
fBood.prototype = new fNOP();
return fBood;
}
到此為止,大的問題都已經(jīng)解決,給自己一個贊!o( ̄▽ ̄)d
小問題
調用 bind 的不是函數(shù)咋辦?
不行,我們要報錯!
if(typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
我要在線上用
那別忘了做個兼容:
Function.prototype.bind = Function.prototype.bind || function () {
……
};
最終代碼
Function.prototype.bind2 = Function.prototype.bind|| function() {
if(typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
var args = [].slice.call(arguments, 1);
var fNOP = function () {};
var fBood = function() {
var newArgs = [].slice.call(arguments);
self.apply(this instanceof fBood? this: context, args.concat(newArgs));
}
fNOP.prototype = this.prototype;
fBood.prototype= new fNOP();
return fBood;
}
加油~~~