你不懂js系列學習筆記-this與對象原型- 01

第一章: this

原文:You-Dont-Know-JSjavascript

JavaScript 中最使人困惑的機制之一就是 this 關鍵字。它是一個在每一個函數做用域中自動定義的特殊標識符關鍵字,但即使是一些老練的 JavaScript 開發者也對它到底指向什麼感到困擾。java

1 爲何要用 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() 函數對多個 環境 對象(meyou)進行復用,而不是針對每一個對象定義函數的分離版本。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

2 困惑

在開發者們用太過於字面的方式考慮 this 這個名字時就會產生困惑。這一般會產生兩種臆測,但都是不對的。this

2.1 它本身

第一種常見的傾向是認爲 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,++以後爲 NaNNaN++以後仍是 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 的詞法做用域。

2.2 它的做用域

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 引用。

相關文章
相關標籤/搜索