其實如果完全掌握了this的工作原理,自然就不會走進這些坑。來看下以下這些情況中的this分別會指向什么:
1.全局代碼中的this
1 |
alert(x); // 全局變量x值為2 |
全局范圍內的this將會指向全局對象,在瀏覽器中即使window。
2.作為單純的函數調用
1
2
3
4
5 |
function fooCoder(x) { this .x = x; } fooCoder(2); alert(x); // 全局變量x值為2 |
這里this指向了全局對象,即window。在嚴格模式中,則是undefined。
3.作為對象的方法調用
1
2
3
4
5
6
7
8 |
var name = "clever coder" ; var person = { name : "foocoder" , hello : function (sth){ console.log( this .name + " says " + sth); } } person.hello( "hello world" ); |
輸出 foocoder says hello world。this指向person對象,即當前對象。
4.作為構造函數
1 |
new FooCoder(); |
5.內部函數
1
2
3
4
5
6
7
8
9
10
11
12 |
var name = "clever coder" ; var person = { name : "foocoder" , hello : function (sth){ var sayhello = function (sth) { console.log( this .name + " says " + sth); }; sayhello( } person.hello( "hello world" ); //clever coder says hello world |
在內部函數中,this沒有按預想的綁定到外層函數對象上,而是綁定到了全局對象。這里普遍被認為是JavaScript語言的設計錯誤,因為沒有人想讓內部函數中的this指向全局對象。一般的處理方式是將this作為變量保存下來,一般約定為that或者self:
1
2
3
4
5
6
7
8
9
10
11
12 |
var name = "clever coder" ; var person = { name : "foocoder" , hello : function (sth){ var that = this ; var sayhello = function (sth) { console.log(that.name + " says " + sth); }; sayhello(sth); } } person.hello( "hello world" ); //foocoder says hello world |
6.使用call和apply設置this
1 |
person.hello.call(person, "world" ); |
1
2
3 |
call( thisArg [,arg1,arg2,… ] ); // 參數列表,arg1,arg2,... apply(thisArg [,argArray] ); // 參數數組,argArray |
兩者都是將某個函數綁定到某個具體對象上使用,自然此時的this會被顯式的設置為第一個參數。
簡單地總結
簡單地總結以上幾點,可以發現,其實只有第六點是讓人疑惑的。
其實就可以總結為以下幾點:
- 當函數作為對象的方法調用時,this指向該對象。
- 當函數作為淡出函數調用時,this指向全局對象(嚴格模式時,為undefined)
- 構造函數中的this指向新創建的對象
- 嵌套函數中的this不會繼承上層函數的this,如果需要,可以用一個變量保存上層函數的this。
再總結的簡單點,如果在函數中使用了this,只有在該函數直接被某對象調用時,該this才指向該對象。
1
2
3 |
obj.foocoder(); foocoder.call(obj, ...); foocoder.apply(obj, …); |
更進一步
我們可能經常會寫這樣的代碼:
1 |
$( "#some-ele" ).click = obj.handler; |
那我們如何能解決回調函數綁定的問題?ES5中引入了一個新的方法,bind():
1
2
3 |
fun.bind(thisArg[, arg1[, arg2[, ...]]]) thisArg |
當綁定函數被調用時,該參數會作為原函數運行時的this指向.當使用new 操作符調用綁定函數時,該參數無效.
1 |
arg1, arg2, ... |
該方法創建一個新函數,稱為綁定函數,綁定函數會以創建它時傳入bind方法的第一個參數作為this,傳入bind方法的第二個以及以后的參數加上綁定函數運行時本身的參數按照順序作為原函數的參數來調用原函數.
顯然bind方法可以很好地解決上述問題。
1
2 |
$( "#some-ele" ).click(person.hello.bind(person)); //相應元素被點擊時,輸出foocoder says hello world |
其實該方法也很容易模擬,我們看下Prototype.js中bind方法的源碼:
1
2
3
4
5
6
7 |
Function.prototype.bind = function (){ var fn = this , args = Array.prototype.slice.call(arguments), object = args.shift(); return function (){ return fn.apply(object, args.concat(Array.prototype.slice.call(arguments))); }; }; |
明白了么?