JavaScript深入之bind的模擬實(shí)現(xiàn)
bind() 方法會(huì)創(chuàng)建一個(gè)函數(shù),當(dāng)這個(gè)新函數(shù)被調(diào)用的時(shí)候,bind() 的第一個(gè)參數(shù)將作為他運(yùn)行時(shí)的this,之后的一序列參數(shù)將會(huì)在傳遞的參數(shù)前作為他的參數(shù)
其實(shí)這也可以看出bind函數(shù)的兩個(gè)特點(diǎn)
- 返回一個(gè)新函數(shù)
- 可以傳入?yún)?shù)
先看一個(gè)例子----返回函數(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,這是因?yàn)槲覀兒瘮?shù)都是由Function創(chuàng)建的,window也是Function.prototype的實(shí)例
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)
//這個(gè)this就指向調(diào)用者。如果是bar函數(shù),就指向bar函數(shù)
//context表示的是傳入的對(duì)象 也就是說
//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
傳參的模擬實(shí)現(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兩個(gè)參數(shù),而且可以在bind的時(shí)候,只傳一個(gè)name,然后再執(zhí)行返回的函數(shù)的時(shí)候再傳入另一個(gè)參數(shù)age
所以我們?cè)俚诙嫘枰獙?duì)arguments進(jìn)行處理
第二版
Function.prototype.bind2 = function(context){
var self = this;
//因?yàn)閍rguments是類數(shù)組對(duì)象,不能使用slice,所以使用call改變this的指向
//從slice(1) 是截取第一個(gè)參數(shù)之后的參數(shù),因?yàn)榈谝粋€(gè)參數(shù)是傳入的對(duì)象context。
var args = Array.prototype.slice.call(arguments,1)
return function(){
//這個(gè)是截取第二個(gè)調(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)
構(gòu)造函數(shù)效果的模擬實(shí)現(xiàn)
一個(gè)綁定函數(shù)也能使用new操作符創(chuàng)建對(duì)象,這種行為就像把函數(shù)當(dāng)做構(gòu)造器,提供的this值被忽略,同時(shí)調(diào)用時(shí)的參數(shù)被提供給模擬函數(shù)
其實(shí)也就是說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');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin
注意:盡管在全局和 foo 中都聲明了 value 值,最后依然返回了 undefind,說明綁定的 this 失效了,如果大家了解 new 的模擬實(shí)現(xiàn),就會(huì)知道這個(gè)時(shí)候的 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);
// 當(dāng)作為構(gòu)造函數(shù)時(shí),this 指向?qū)嵗藭r(shí)結(jié)果為 true,將綁定函數(shù)的 this 指向該實(shí)例,可以讓實(shí)例獲得來自綁定函數(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ù)的原型中的值
//這里實(shí)際吧fBound 作為function,也就是第一調(diào)用的時(shí)候就返回一個(gè)函數(shù),函數(shù)里面再根據(jù)調(diào)用的方式,確定this的指向,當(dāng)?shù)诙沃赶虻臅r(shí)候,this指向的開始改變并賦值給fBound.prototype 就可以改變this的指向
fBound.prototype = this.prototype;
return fBound;
}
構(gòu)造函數(shù)效果的優(yōu)化實(shí)現(xiàn)
但是在這個(gè)寫法中,我們直接將 fBound.prototype = this.prototype,我們直接修改 fBound.prototype 的時(shí)候,也會(huì)直接修改綁定函數(shù)的 prototype。這個(gè)時(shí)候,我們可以通過一個(gè)空函數(shù)來進(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.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;
}
以上參考一下文章加了一些自己的理解