原文:Understanding the "this" keyword in JavaScriptjavascript
許多人被JavaScript中的this
關鍵字給困擾住了,我想混亂的根源來自人們理所固然地認爲JavaScript中的this
應該像Java中的this
或Python中的self
同樣工做。儘管JavaScript的this
有時有相似的效果,但它跟Java或其餘語言中的this
是徹底不一樣的。儘管有點難理解,但它的原理並不神祕。事實上,this
遵循一些簡單的小規則,這篇文章就對這些規則做出瞭解釋。java
但首先,我想給出一些專業術語和一些JavaScript運行時環境的相關信息,但願能幫助你構建一個心智模型來更好的理解this
。數組
每一行JavaScript代碼都運行在一個「執行上下文」中。JavaScript運行時環境用一個棧維持着這些上下文,棧頂的一個就是當前正在運行的執行上下文。瀏覽器
有三類可執行代碼:全局代碼(global code),函數代碼(function code),eval代碼(eval code)。大概的說,全局代碼是應用程序最頂層的代碼,不被包含在任何方法中,函數代碼是在函數(function)體中的代碼,eval代碼是被eval
解釋的全局代碼。app
this
對象每次控制進入一個新的執行上下文時會被從新肯定指向,直到控制切換到一個不一樣的上下文。this
的值取決於兩件事:被執行的代碼的類型(global,function,eval)和調用代碼的對象。ide
全部的JavaScript運行時都有一個惟一的全局對象。他的屬性包括內置的對象如Math
和String
,以及其餘由主環境變量定義的屬性。函數
在瀏覽器中,全局對象是window
對象。在Node.js中,它就叫做「global object」。(我假定你將在瀏覽器中運行代碼,然而,我所說的一切也一樣適用於Node.js。)this
this
的值第一條規則很簡單:this
指向全局對象在全部全局代碼中。因爲全部的程序都是由執行全局代碼開始的,而且this
在給定的執行上下文中會被修正,因此,默認的this
指全局對象。lua
當控制進入一個新的執行上下文時發生了什麼呢?只有三種this
的值發生改變的狀況:方法調用,new一個函數對象,函數被call
和apply
調用。我將逐一解釋。excel
當咱們經過點(例obj.foo()
)或者方括號(例obj["foo"]
)把一個方法做爲一個對象的屬性來調用時,this
將指向方法體的父對象:
var counter = { val: 0, increment: function () { this.val += 1; } }; counter.increment(); console.log(counter.val); // 1 counter['increment'](); console.log(counter.val); // 2
這是第二條規則:函數被看成父對象的屬性來調用時,在函數代碼中的this
指向函數的父對象。
注意,若是咱們直接調用相同的方法,即,不做爲父對象的屬性,this
將不會指向counter
對象:
var inc = counter.increment; inc(); console.log(counter.val); // 2 console.log(val); // NaN
當inc
被調用時,這裏的this
不會改變,因此它仍是指向全局對象。
當inc
執行
this.val += 1;
它等效於執行:
window.val += 1;
window.val
是undefined,且undefined
加1
獲得NaN
。
任何JavaScript函數均可以經過new
運算符當成構造函數使用。new
運算符建立一個新對象而且設置函數中的this
指向調用函數的新對象。舉個栗子:
function F (v) { this.val = v; } var f = new F("Woohoo!"); console.log(f.val); // Woohoo! console.log(val); // ReferenceError
這就是咱們的第三條規則:在用new
運算符調用的函數代碼中的this
指向新建立的對象。
注意F
沒有任何特殊。若是不經過new
調用,this
將指向全局對象:
var f = F("Oops!"); console.log(f.val); // undefined console.log(val); // Oops!
在這個例子中,F("Oops!")
是一個一般調用,this
沒有指向新的對象,由於沒有用new
建立新的對象,this
繼續指向全局對象。
全部JavaScript函數都有兩個方法,call
和apply
,讓你可以調用函數而且清楚的設置this
指向的對象。apply
函數有兩個參數:一個是this
指向的對象,一個(可選)傳進函數的參數的數組:
var add = function (x, y) { this.val = x + y; }, obj = { val: 0 }; add.apply(obj, [2, 8]); console.log(obj.val); // 10
call
方法和apply
方法是徹底同樣的,只不過要逐個的傳遞參數,而不是用一個數組:
add.call(obj, 2, 8); console.log(obj.val); // 10
這是第四條規則:當使用call
或apply
調用函數時,函數代碼中的this
被設置爲call
或apply
中的第一個參數。
this
指向全局對象this
指向父對象new
運算符調用時,函數中的this
指向新建立的對象call
或apply
調用函數時,函數代碼中的this
被設置爲call
或apply
中的第一個參數。若是第一個參數是null
或不是個對象,this
指向全局對象。若是你理解並接受了上面的4條規則,你就能明白this
指的是什麼了。
eval
打破上面全部規則Remember when I said that code evaluated inside eval
is its own type of executable code? Well, that’s true, and it means that the rules for determining what this
refers to inside of eval code are a little more complex.
As a first pass, you might think that this
directly inside eval refers to the same object as it does in eval
’s caller’s context. For example:
var obj = { val: 0, func: function() { eval("console.log(this.val)"); } }; obj.func(); // 0
That works likely as you expect it to. However, there are many cases with eval
where this
will probably not work as you expect:
eval.call({val: 0}, "console.log(this.val)"); // depends on browser
The output of the above code depends on your browser. If your JavaScript runtime implements ECMAScript 5, this
will refer to the global object and the above should print undefined
, because it’s an 「indirect」 call of eval
. That’s what the latest versions of Chrome and Firefox do. Safari 5.1 actually throws an error (「The ‘this’ value passed to eval must be the global object from which eval originated」), which is kosher according to ECMAScript 3.
If you want know how this
works with eval
, you should read Juriy Zaytsev’s excellent, 「Global eval. What are the options?」 You’ll learn more about eval
, execution contexts, and direct vs. indirect calls than you probably ever wanted to know.
In general, you should just avoid using eval
, in which case the simple rules about this
given previously will always hold true.