原文:You-Dont-Know-JSjavascript
JavaScript 中最使人困惑的機制之一就是 this
關鍵字。它是一個在每一個函數做用域中自動定義的特殊標識符關鍵字,但即使是一些老練的 JavaScript 開發者也對它到底指向什麼感到困擾。java
this
?讓咱們試着展現一下 this
的動機和用途:git
function identify() {
return this.name.toUpperCase();
}
function speak() {
var greeting = "Hello, I'm " + identify.call(this);
console.log(greeting);
}
var me = {
name: "Kyle"
};
var you = {
name: "Reader"
};
identify.call(me); // KYLE
identify.call(you); // READER
speak.call(me); // Hello, I'm KYLE
speak.call(you); // Hello, I'm READER
複製代碼
這個代碼片斷容許 identify()
和 speak()
函數對多個 環境 對象(me
和 you
)進行復用,而不是針對每一個對象定義函數的分離版本。github
與使用 this
相反地,你能夠明確地將環境對象傳遞給 identify()
和 speak()
。ide
function identify(context) {
return context.name.toUpperCase();
}
function speak(context) {
var greeting = "Hello, I'm " + identify(context);
console.log(greeting);
}
identify(you); // READER
speak(me); // Hello, I'm KYLE
複製代碼
然而,this
機制提供了更優雅的方式來隱含地「傳遞」一個對象引用,致使更加乾淨的API設計和更容易的複用。函數
你的使用模式越複雜,你就會越清晰地看到:將執行環境做爲一個明確參數傳遞,一般比傳遞 this
執行環境要亂。當咱們探索對象和原型時,你將會看到一組能夠自動引用恰當執行環境對象的函數是多麼有用。ui
在開發者們用太過於字面的方式考慮 this
這個名字時就會產生困惑。這一般會產生兩種臆測,但都是不對的。this
第一種常見的傾向是認爲 this
指向函數本身。至少,這是一種語法上的合理推測。spa
爲何你想要在函數內部引用它本身?最多見的理由是遞歸(在函數內部調用它本身)這樣的情形,或者是一個在第一次被調用時會解除本身綁定的事件處理器。設計
考慮下面的代碼,咱們試圖追蹤函數(foo
)被調用了多少次:
function foo(num) {
console.log("foo: " + num);
// 追蹤 `foo` 被調用了多少次
this.count++;
}
foo.count = 0;
var i;
for (i = 0; i < 10; i++) {
if (i > 5) {
foo(i);
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// `foo` 被調用了多少次?
console.log(foo.count); // 0
複製代碼
當代碼執行 foo.count = 0
時,它確實向函數對象 foo
添加了一個 count
屬性。可是對於函數內部的 this.count
引用,this
其實 根本就不 指向那個函數對象,即使屬性名稱同樣,但根對象也不一樣,於是產生了混淆。實際上,若是他再挖的深一些,他會發現本身不當心建立了一個全局變量 count
(第二章解釋了這是 如何 發生的!),並且它當前的值是 NaN
。(全局變量 count
初始值爲undefined
,++以後爲 NaN
, NaN
++以後仍是 NaN
)
對於當前咱們的例子來講,另外一個 好用的 解決方案是在每個地方都使用 foo
標識符做爲函數對象的引用,而根本不用this
:
function foo(num) {
console.log("foo: " + num);
// 追蹤 `foo` 被調用了多少次
foo.count++;
}
foo.count = 0;
var i;
for (i = 0; i < 10; i++) {
if (i > 5) {
foo(i);
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// `foo` 被調用了多少次?
console.log(foo.count); // 4
複製代碼
然而,這種方法也相似地迴避了對 this
的 真正 理解,並且徹底依靠變量 foo
的詞法做用域。
對 this
的含義第二常見的誤解,是它不知怎的指向了函數的做用域。這是一個刁鑽的問題,由於在某一種意義上它有正確的部分,而在另一種意義上,它是嚴重的誤導。
明確地說,this
不會以任何方式指向函數的 詞法做用域。做用域好像是一個將全部可用標識符做爲屬性的對象,這從內部來講是對的。可是 JavasScript 代碼不能訪問做用域「對象」。它是 引擎的內部實現。
考慮下面代碼,它(失敗的)企圖跨越這個邊界,用 this
來隱含地引用函數的詞法做用域:
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log(this.a);
}
foo(); //undefined
//兩個this都指向 window,試圖經過 this.bar() 來引用 bar() 函數,可以執行僅僅是巧合。
複製代碼
不能使用 this
引用在詞法做用域中查找東西。這是不可能的。
咱們早先說過,this
不是編寫時綁定,而是運行時綁定。它依賴於函數調用的上下文條件。this
綁定與函數聲明的位置沒有任何關係,而與函數被調用的方式緊密相連。
當一個函數被調用時,會創建一個稱爲執行環境的活動記錄。這個記錄包含函數是從何處(調用棧 —— call-stack)被調用的,函數是如何被調用的,被傳遞了什麼參數等信息。這個記錄的屬性之一,就是在函數執行期間將被使用的 this
引用。