原文出處
JavaScript深入之bind的模擬實(shí)現(xiàn)
bind
我們?cè)谀M bind之前切油,先看看 bind實(shí)現(xiàn)了哪些功能矾瑰。
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
// 返回了一個(gè)函數(shù)
var bindFoo = bar.bind(foo);
bindFoo(); // 1
由此我們可以首先得出 bind 函數(shù)的兩個(gè)特點(diǎn):
- 返回一個(gè)函數(shù)
- 改變了this的指向
模擬實(shí)現(xiàn)第一步
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
// 第一版
Function.prototype.bind2 = function (context) {
var self = this;
return function () { // 返回一個(gè)函數(shù)
return self.apply(context); // 改變綁定函數(shù)的this指向
}
}
// 返回了一個(gè)函數(shù)
var bindFoo = bar.bind2(foo);
bindFoo(); // 1
模擬實(shí)現(xiàn)第二步
舉個(gè)例子:
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
由此我們可以看出:
- bind 綁定的時(shí)候可以傳參數(shù)
- 在執(zhí)行返回函數(shù)的時(shí)候也可以傳參數(shù)
我們可以用 arguments 模擬,如下:
var foo = {
value: 1
};
function bar(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
// 第二版
Function.prototype.bind2 = function (context) {
var self = this;
// 獲取bind2函數(shù)從第二個(gè)參數(shù)到最后一個(gè)參數(shù)
var args = Array.prototype.slice.call(arguments, 1);
return function () {
// 這個(gè)時(shí)候的arguments是指bind返回的函數(shù)傳入的參數(shù)
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(context, args.concat(bindArgs));
}
}
// 返回了一個(gè)函數(shù)
var bindFoo = bar.bind2(foo, 'daisy');
bindFoo('18');
// 1
// daisy
// 18
模擬實(shí)現(xiàn)第三步
舉個(gè)例子
bind 還有一個(gè)特點(diǎn)拴清,就是
一個(gè)綁定函數(shù)也能使用new操作符創(chuàng)建對(duì)象:這種行為就像把原函數(shù)當(dāng)成構(gòu)造器。提供的 this 值被忽略,同時(shí)調(diào)用時(shí)的參數(shù)被提供給模擬函數(shù)先誉。
也就是說(shuō)當(dāng) bind 返回的函數(shù)作為構(gòu)造函數(shù)的時(shí)候,bind 時(shí)指定的 this 值會(huì)失效的烁,但傳入的參數(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');
console.log(obj instanceof bar);
// undefined
// daisy
// 18
// true
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin
注意:盡管在全局和 foo 中都聲明了 value 值,最后依然返回了 undefind渴庆,說(shuō)明綁定的 this 失效了铃芦,這時(shí)候 this 已經(jīng)指向 bar 實(shí)例了雅镊。
所以我們可以通過(guò)修改返回的函數(shù)的原型來(lái)實(shí)現(xiàn),讓我們寫一下:
// 第三版
Function.prototype.bind2 = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
// 當(dāng)作為構(gòu)造函數(shù)時(shí)刃滓,this 指向?qū)嵗逝耄藭r(shí)結(jié)果為 true,將綁定函數(shù)的 this 指向該實(shí)例注盈,可以讓實(shí)例獲得來(lái)自綁定函數(shù)的值
// 以上面的是 demo 為例晃危,如果改成 `this instanceof fBound ? null : context`,實(shí)例只是一個(gè)空對(duì)象老客,將 null 改成 this 僚饭,實(shí)例會(huì)具有 habit 屬性
// 當(dāng)作為普通函數(shù)時(shí),this 指向 window胧砰,此時(shí)結(jié)果為 false鳍鸵,將綁定函數(shù)的 this 指向 context
return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
}
// 修改返回函數(shù)的 prototype 為綁定函數(shù)的 prototype,實(shí)例就可以繼承綁定函數(shù)的原型中的值
fBound.prototype = this.prototype;
return fBound;
}
注意:bind2 中的 this 與 fBound 中的 this 不一樣尉间, 前者是綁定函數(shù)對(duì)象偿乖,后者如果返回函數(shù)作為構(gòu)造函數(shù)調(diào)用,那么 this 指向返回函數(shù)實(shí)例對(duì)象哲嘲, 如果當(dāng)作普通函數(shù)調(diào)用的話贪薪,this 指向 window。
模擬實(shí)現(xiàn)第四步
但是在這個(gè)寫法中眠副,我們直接將 fBound.prototype = this.prototype画切,我們直接修改 fBound.prototype 的時(shí)候,也會(huì)直接修改綁定函數(shù)的 prototype囱怕。
舉例:
Function.prototype.bind2 = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
}
fBound.prototype = this.prototype;
return fBound;
}
function bar() {}
var bindFoo = bar.bind2(null);
bindFoo.prototype.value = 1;
console.log(bar.prototype.value) // 1
這個(gè)時(shí)候霍弹,我們可以通過(guò)一個(gè)空函數(shù)來(lái)進(jìn)行中轉(zhuǎn):
// 第四版
Function.prototype.bind2 = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP(); // 原型鏈繼承
return fBound;
}
function bar() {}
bar.prototype.friend = 'kevin';
bar.prototype.value = 2;
var bindFoo = bar.bind2(null);
bindFoo.prototype.value = 1;
console.log(bar.prototype.value) // 2
console.log(bindFoo.prototype.value); // 1
var obj = new bindFoo();
console.log(obj.friend); // kevin