首先,什麼是軟繫結?
所謂軟繫結,是和硬繫結相對應的一個詞,在詳細解釋軟繫結之前,我們先來看看硬繫結。在JavaScript中,this的繫結是動態的,在函式被呼叫的時候繫結,它指向什麼完全取決於函式在哪裡呼叫,情況比較複雜,光是繫結規則就有預設繫結、隱式繫結、顯式繫結、new繫結等,而硬繫結是顯式繫結中的一種,通常情況下是通過呼叫函式的apply()
、call()
或者ES5裡提供的bind()
方法來實現硬繫結的。
硬繫結有什麼問題,為什麼需要軟繫結
上述三個方法好是好,可以按照自己的想法將函式的this強制繫結到指定的物件上(除了使用new繫結可以改變硬繫結外),但是硬繫結存在一個問題,就是會降低函式的靈活性,並且在硬繫結之後無法再使用隱式繫結或者顯式繫結來修改this的指向。
在這種情況下,被稱為軟繫結的實現就出現了,也就是說,通過軟繫結,我們希望this在預設情況下不再指向全域性物件(非嚴格模式)或undefined
(嚴格模式),而是指向兩者之外的一個物件(這點和硬繫結的效果相同),但是同時又保留了隱式繫結和顯式繫結在之後可以修改this指向的能力。
軟繫結的具體實現
在這裡,我用的是《你不知道的JavaScript 上》中的軟繫結的程式碼實現:
if(!Function.prototype.softBind){
Function.prototype.softBind=function(obj){
var fn=this;
var args=Array.prototype.slice.call(arguments,1);
var bound=function(){
return fn.apply(
(!this||this===(window||global))?obj:this,
args.concat.apply(args,arguments)
);
};
bound.prototype=Object.create(fn.prototype);
return bound;
};
}
複製程式碼
我們先來看一下效果,之後再討論它的實現。
function foo(){
console.log("name: "+this.name);
}
var obj1={name:"obj1"},
obj2={name:"obj2"},
obj3={name:"obj3"};
var fooOBJ=foo.softBind(obj1);
fooOBJ();//"name: obj1" 在這裡軟繫結生效了,成功修改了this的指向,將this繫結到了obj1上
obj2.foo=foo.softBind(obj1);
obj2.foo();//"name: obj2" 在這裡軟繫結的this指向成功被隱式繫結修改了,繫結到了obj2上
fooOBJ.call(obj3);//"name: obj3" 在這裡軟繫結的this指向成功被硬繫結修改了,繫結到了obj3上
setTimeout(obj2.foo,1000);//"name: obj1"
/*回撥函式相當於一個隱式的傳參,如果沒有軟繫結的話,這裡將會應用預設繫結將this繫結到全域性環
境上,但有軟繫結,這裡this還是指向obj1*/
複製程式碼
可以看到軟繫結生效了。下面我們來具體看一下softBind()
的實現。
在第一行,先通過判斷,如果函式的原型上沒有softBind()
這個方法,則新增它,然後通過Array.prototype.slice.call(arguments,1)
獲取傳入的外部引數,這裡這樣做其實為了函式柯里化,也就是說,允許在軟繫結的時候,事先設定好一些引數,在呼叫函式的時候再傳入另一些引數(關於函式柯里化大家可以去網上搜一下詳細的講解)最後返回一個bound
函式形成一個閉包,這時候,在函式呼叫softBind()
之後,得到的就是bound
函式,例如上面的var fooOBJ=foo.softBind(obj1)
。
在bound
函式中,首先會判斷呼叫軟繫結之後的函式(如fooOBJ)的呼叫位置,或者說它的this的指向,如果!this
(this指向undefined)或者this===(window||global)
(this指向全域性物件),那麼就將函式的this繫結到傳入softBind
中的引數obj上。如果此時this不指向undefind或者全域性物件,那麼就將this繫結到現在正在指向的函式(即隱式繫結或顯式繫結)。fn.apply
的第二個引數則是執行foo
所需要的引數,由上面的args(外部引數)和內部的arguments(內部引數)連線成,也就是上面說的柯里化。
其實在第一遍看這個函式時,也有點迷,有一些疑問,比如var fn=this
這句,在foo
通過foo.softBind()
呼叫softBind
的時候,fn到底指向誰呢?是指向foo還是指向softBind?我們可以寫個demo測試,然後可以很清晰地看出fn指向什麼:
var a=2;
function foo(){
}
foo.a=3;
Function.prototype.softBind=function(){
var fn=this;
return function(){
console.log(fn.a);
}
};
Function.prototype.a=4;
Function.prototype.softBind.a=5;
foo.softBind()();//3
Function.prototype.softBind()();//4
複製程式碼
可以看出,fn(或者說this)的指向還是遵循this的繫結規則的,softBind
函式定義在Function的原型Function.prototype
中,但是JavaScript中函式永遠不會“屬於”某個物件(不像其他語言如java中類裡面定義的方法那樣),只是物件內部引用了這個函式,所以在通過下面兩種方式呼叫時,fn(或者說this)分別隱式繫結到了foo
和Function.prototype
,所以分別輸出3和4。後面的fn.apply()
也就相當於foo.apply()
。
第一次在掘金寫部落格,以後多寫點吧,也算是對知識點的一個總結,同時也希望能幫到大家。emmm 就這樣。