1、什么是this
在JavaScript中this可以是全局對象、當前對象或者任意對象,這完全取決於函數的調用方式,this 綁定的對象即函數執行的上下文環境(context)。
為了幫助理解,讓我們來一起看一段代碼:
// 作為對象方法調用 var test = { a : 5, b : 6, sum : function () { return this.a + this.b; // 此處this = test } } alert(test.sum()); // 11
作為對象調用時this很容易理解,this等價於sum的調用者即上訴的test對象,如果作為函數調用時this=?
// 作為函數調用 a = 4; b = 3; function sum(){ return this.a + this.b; // 此處this = window } alert(sum()); // 7
此時函數sum是做為window對象的一個全局函數,因此sum的調用者為window,即this = window。
var test = { a : 5, b : 6, sum : function (a,b) { function getA(a) { this.a = a; // 在window上增加了一個全局變量a return this.a; // 此處this = window } function getB(b){ this.b = b; //在window上增加了一個全局變量b return this.b; // 此處this = window } return getA(a) + getB(b); } } alert(test.sum(4,3)); // 7 alert(a); // 4 alert(b); // 3
在這種情況下,我們希望getA() 和getB() 返回的值是test.a和test.b,但是此時閉包函數(即函數中的函數)getA和getB中this並不指向test的實例,該怎么辦呢?我們不妨試試下面的方法:
var test = { a : 5, b : 6, sum : function () { var self = this; // 此處this = test的實例 function getA() { return self.a; } function getB(){ return self.b; } return getA() + getB(); } } alert(test.sum()); alert(a); // 此處報錯:a is not defined alert(b); // 此處報錯:a is not defined
在test對象的sum函數中用一個局部變量self來保存當前的this指針,這樣在閉包函數getA和getB中就能通過self變量獲取test實例的屬性了。
看起來這樣就能夠解決閉包函數中this的問題了,但是,如果調用sum函數的並不是test的實例呢,這個時候var self=this還能起到作用,獲取到test的實例嗎?
2、使用call、apply和bind改變函數執行時的上下文(this)
使用call、apply和bind都能夠是函數的上下文發生改變,那我們來具體看看這記者之間的區別吧。
call方法:
語法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])
定義:調用一個對象的一個方法,以另一個對象替換當前對象。
說明:call 方法可以用來代替另一個對象調用一個方法。call 方法可將一個函數的對象上下文從初始的上下文改變為由 thisObj 指定的新對象。
如果沒有提供 thisObj 參數,那么 Global 對象被用作 thisObj。
apply方法:
語法:apply([thisObj[,argArray]])
定義:應用某一對象的一個方法,用另一個對象替換當前對象。
說明:如果 argArray 不是一個有效的數組或者不是 arguments 對象,那么將導致一個 TypeError。
如果沒有提供 argArray 和 thisObj 任何一個參數,那么 Global 對象將被用作 thisObj, 並且無法被傳遞任何參數。
bind方法:
語法:bind(thisArg[, arg1[, arg2[, ...]]])
定義:將接受多個參數的函數變換成接受一個單一參數。
說明:bind()方法所返回的函數的length(形參數量)等於原函數的形參數量減去傳入bind()方法中的實參數量(第一個參數以后的所有參數),因為傳入bind中的實參都會綁定到原函數的形參。
哎呀媽呀,講了那么多理論的東西,我都暈了,還是看看實際的例子:
var test = { a : 5, b : 6, sum : function (a,b) { var self = this; function getA() { return self.a; } function getB(){ return self.b; } alert(a); alert(b); return getA() + getB(); } } var obj = {a:2,b:3}; alert(test.sum.call(obj,4,5)); // 調用時self = this = obj,alert順序4,5,5 alert(test.sum.apply(obj,[6,7])); // 調用時self = this = obj,alert順序6,7,5 var sum = test.sum.bind(obj,8); // 此處返回一個只有一個參數的函數sum(b) alert(sum(9)); // 調用時self = this = obj,alert順序8,9,5
從上面的例子我們可以很清晰的看到call、apply和bind之間的區別。其中call和apply是差不多的,只是傳參的形勢不同(apply的第二個參數為一個數組或arguments),他們都是直接直接執行函數;
而bind函數將test.sum簡化為另一個全局函數sum(b),sum(b)只需要傳入一個參數即可。
3、解決js中煩人的this
call、apply和bind都可以應用於繼承,在這里不再過多贅述,網上有很多這樣的例子,參考:http://blog.csdn.net/wyyfwm/article/details/46349071
而我想講一下這段時間我遇到的一些關於this比較頭疼的事情。
<button id="btn">煩人的this</button> <script> var test = { isSum: true, sum: function (event, a, b) { if (this.isSum) { // this = button,這個時候不會執行alert(a+b) alert(a + b); } } } var button = document.getElementById("btn"); button.addEventListener("click", test.sum, false); </script>
這里我們就能發現問題所在了,當ID為btn的按鈕被點擊時會觸發test.sum函數,但是這個時候的this=button,而且參數a、b如何傳入呢?
這里就能夠使用bind函數了,將test.sum函數簡化為另一個新的函數,同時傳入參數a和b,我們再看看下面的代碼:
<button id="btn">this</button> <script> var test = { isSum: true, sum: function (a, b,event) { if (this.isSum) { // 此處this=test,this.isSum = true alert(a + b); // 9 } } } var button = document.getElementById("btn"); button.addEventListener("click", test.sum.bind(test,4,5), false); // 此處test.sum.bind(test,4,5)返回一個新的函數function(event),
</script>
從上面的代碼我們可以看到test.sum.bind(test,4,5)返回一個新的函數function(event),test、4、5分別被綁定到test.sum的上下文、參數a、參數b中。
當ID為btn的按鈕被點擊時會觸發test.sum函數,此時改函數中的this=test,a=4,b=5。
這樣就可以解決事件綁定時的this以及傳參的問題了,包括現在常用js框架中的事件綁定,如jQuery、signals.min.js等等。