本文默認讀者已經對this有了一定了解,如果讀者覺得自己對this定義或者this的簡單指向還有疑問,請先看我的另一篇文章。
this直譯過來就是這個,它會被自動定義到所有函數的作用域中,不過這也造成了很多困擾,因爲經常很難分辨它到底代表着什麼,但由於它自身的功能確實可以提供很多便利,所以程序員不管能不能掌握this,不管會經常報錯,還是會樂此不彼的使用它。
那麼this到底是什麼?其實它是函數在運行時根據一定規則綁定到變量上的,在函數被調動時,會創建一個調用的記錄,也被稱爲執行上下文,這個記錄中記錄着調用棧(函數的調用次序),函數參數,調用方式等,this是這個記錄的一個屬性。
簡單介紹一下四種綁定機制:默認綁定(函數調用),隱式綁定(方法調用),顯式綁定(call,apply調用),new綁定(構造器調用)。這四種機制已經在另一篇文章介紹過了,這裏就不再贅述了,我們今天要討論的是這四種機制的優先級和一些特殊情況。
優先級:
毫無疑問,默認綁定是最低級別的,所以我們就不考慮它了。判斷一下顯式綁定和隱式綁定
var foo = function foo(){
console.log(this.a);
}
var obj1 = {
a: 1,
foo: foo
};
var obj2 = {
a:2,
foo: foo
};
obj1.foo();//1
obj2.foo();//2
obj1.foo().call(obj2);//2
obj2.foo().call(obj1);//1
所以顯示綁定比隱式綁定優先級更高。
接下來判斷一下new綁定和顯示綁定
var foo = function foo( param ){
this.a = param;
};
var obj1 = {};
var F = foo.bind(obj1);
F(1);
console.log(obj1.a);//1
var f = new F(2);
console.log(obj1.a);//1
console.log(f.a);//2
可以看出在new了一個實例後,其返回的新對象已經被改變了this綁定,但是其實原來的對象並沒有被改變。所以最後的結果就是new綁定 顯式綁定 隱式綁定 默認綁定。
但是我們的js總是會給我們帶來無限驚喜,這些規則也並不是鐵律。在使用apply、call時,我們會遇到一種場景:不需要指定this的指向,只是想利用一下它的後面傳入的參數,這時我們經常會將null傳入作爲第一個參數,然而這會導致一個非常不好的後果,這時this其實會指向全局對象,也就是其實會使用默認規則,其實這也好理解,你把它指向設置爲null,它不變成null就只能恢復默認設置了。這裏我們提供一種解決方案,傳入一個空的對象,這樣就不會對全局對象產生影響了。
另外介紹一下軟綁定,顧名思義與硬綁定相對,軟綁定是指給函數綁定一個非全局變量和undefined的值,保證不會讓this恢復默認綁定的同時還給以後靈活改變this綁定留有餘地。下面給出軟綁定的實現。
if( !Function.prototype.softBind ){//是否已經存在softBind函數
Function.prototype.softBind = function(obj){
//傳入要軟綁定this的對象以及參數
var fn = this;//保留原函數的引用,也就是Function
var curried = [].slice.call( arguments, 1 );//提取出傳入參數的非綁定對象部分
var bound = function(){
return fn.apply(
( !this || this === (window || global )) ? obj : this,//如果this對象不爲空或者不是全局對象就綁定傳入的對象 this由bound的調用方式決定
curried.concat.apply( curried, arguments )//將初始參數和新參數合併
);
};
bound.prototype = Object.create( fn.prototype );//創建一個繼承原函數原型的空對象,並將bound的原型設爲它
return bound;
}
}
function foo(){
console.log( "name: " + this.name );
}
var obj = { name: "obj" },
obj2 = { name: "obj2" },
obj3 = { name: "obj3" };
var fooOBJ = foo.softBind( obj );
fooOBJ();// obj
obj2.foo = foo.softBind( obj );
obj2.foo();//obj2
fooOBJ.call( obj3 );//obj3
setTimeout( obj2.foo, 10 );//obj
其實代碼思路很簡單,就是在call、apply系列函數的基礎上加了一個額外的判定功能,判定this是不是默認綁定,如果是默認綁定則啓用我們軟綁定的對象,其根本目的就是防止this默認綁定,也可以理解爲我們手動給this設置了默認綁定。
另外還有一個需要注意的地方是es6中定義的箭頭函數,他的this是直接根據外層作用域來決定的。這裏就多敘述了,我將在接下來es6專題中討論它。