JavaScript深入之bind的模擬實現(xiàn)
bind() 方法會創(chuàng)建一個函數(shù)拜马,當這個新函數(shù)被調(diào)用的時候,bind() 的第一個參數(shù)將作為他運行時的this,之后的一序列參數(shù)將會在傳遞的參數(shù)前作為他的參數(shù)
其實這也可以看出bind函數(shù)的兩個特點
- 返回一個新函數(shù)
- 可以傳入?yún)?shù)
先看一個例子----返回函數(shù)的實現(xiàn)
var foo = {
value : 2
}
function bar(){
console.log(this.value)
}
var bindBar = bar.bind(foo)
bindBar();
// 2
我們定義的bind2 的函數(shù)要定義在Function.prototype,這是因為我們函數(shù)都是由Function創(chuàng)建的,window也是Function.prototype的實例
window.__proto__.__proto__.__proto__.__proto__.__proto__ == Object.prototype.__proto__
//true
Object.__proto__ == Function.prototype
//true
window.__proto__.__proto__.__proto__.__proto__ == Function.prototype.__proto__
//true
Function.prototype.bind2 = function(context){
console.log(111, this)
//這個this就指向調(diào)用者。如果是bar函數(shù)坟漱,就指向bar函數(shù)
//context表示的是傳入的對象 也就是說
//bar函數(shù)中的this指向context中的this
var self = this
return function(){
self.apply(context)
}
}
var foo = {
value:3
}
function bar(){
console.log(this.value)
}
var bindFoo = bar.bind2(foo);
//bind2 返回的只是bind2中的新函數(shù),所以需要再調(diào)用一次
console.log(bindFoo()) //3
傳參的模擬實現(xiàn)
先看騷騷的bind傳參,繼續(xù)剛才的騷例子
var foos = {
value:4
}
function foo(name,age){
console.log(name)
console.log(age)
console.log(this.value)
}
var bindFoo = foo.bind(foos, 'xiaolizi')
bindFoo(18)
// xiaolizi
//18
// 4
foo需要傳入name和age兩個參數(shù)更哄,而且可以在bind的時候芋齿,只傳一個name,然后再執(zhí)行返回的函數(shù)的時候再傳入另一個參數(shù)age
所以我們再第二版需要對arguments進行處理
第二版
Function.prototype.bind2 = function(context){
var self = this;
//因為arguments是類數(shù)組對象成翩,不能使用slice觅捆,所以使用call改變this的指向
//從slice(1) 是截取第一個參數(shù)之后的參數(shù),因為第一個參數(shù)是傳入的對象context麻敌。
var args = Array.prototype.slice.call(arguments,1)
return function(){
//這個是截取第二個調(diào)用的函數(shù)栅炒,所以截取全部的參數(shù)
var bindArgs = Array.prototype.slice.call(arguments)
return self.apply(context,args.concat(bindArgs))
}
}
var foos = {
value:4
}
function foo(name,age){
console.log(name)
console.log(age)
console.log(this.value)
}
var bindFoo = foo.bind2(foos, 'xiaolizi')
bindFoo(18)
構造函數(shù)效果的模擬實現(xiàn)
一個綁定函數(shù)也能使用new操作符創(chuàng)建對象,這種行為就像把函數(shù)當做構造器术羔,提供的this值被忽略赢赊,同時調(diào)用時的參數(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嚼贡。
// 第三版
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);
// 當作為構造函數(shù)時熏纯,this 指向?qū)嵗藭r結果為 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 fBound ? this : context, args.concat(bindArgs));
}
// 修改返回函數(shù)的 prototype 為綁定函數(shù)的 prototype嚷堡,實例就可以繼承綁定函數(shù)的原型中的值
//這里實際吧fBound 作為function,也就是第一調(diào)用的時候就返回一個函數(shù),函數(shù)里面再根據(jù)調(diào)用的方式蝌戒,確定this的指向串塑,當?shù)诙沃赶虻臅r候,this指向的開始改變并賦值給fBound.prototype 就可以改變this的指向
fBound.prototype = this.prototype;
return fBound;
}
構造函數(shù)效果的優(yōu)化實現(xiàn)
但是在這個寫法中北苟,我們直接將 fBound.prototype = this.prototype桩匪,我們直接修改 fBound.prototype 的時候,也會直接修改綁定函數(shù)的 prototype友鼻。這個時候傻昙,我們可以通過一個空函數(shù)來進行中轉(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.prototype.bind2 = function (context) {
if (typeof this !== "function") {
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
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;
}
以上參考一下文章加了一些自己的理解