JavaScript初學者必看“this”

Fundebug發表於2017-05-25

譯者按: JavaScript的this和Java等面嚮物件語言中的this大不一樣,bind()call()apply()函式更是將this的靈活度進一步延伸。

為了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原作者所有,翻譯僅用於學習。

如果對JavaScript的關鍵字this理解不夠深刻,有時候會掉入意想不到的坑。在這裡我們總結了5條通用規則來幫助你判斷this到底指向什麼。雖然沒有囊括所有的情況,但日常大部分情況都可以使用這些規則來正確推斷。

  1. this的值通常是由所在函式的執行環境決定,也就是說要看函式是如何被呼叫的;
  2. 同一個函式每一次呼叫,this都可能指向不同的物件;

全域性物件 (Global Object)

開啟Chrome瀏覽器開發者皮膚(Windows: Ctrl + Shift + J)(Mac: Cmd + Option + J),並且輸入:

console.log(this);
複製程式碼

看看輸出了什麼?

// Window {}
複製程式碼

window物件! 因為在全域性作用域下,this指向全域性物件。在瀏覽器中全域性物件就是window物件。 為了讓你更加清楚理解為什麼this會指向window物件,我們來看另外一個例子:

var myName = 'Brandon';
複製程式碼

我們可以通過在控制檯輸入myName來訪問其值:

myName
// 輸出 'Brandon'
複製程式碼

其實,所有在全域性定義的變數都繫結到window物件。我們來做如下測試:

window.myName
// 輸出 'Brandon'
window.myName === myName
// 輸出 true
複製程式碼

現在我們將this放到函式內部,看看有何效果。

function test(){
  return this;
}
test();
複製程式碼

你會發現this依然指向全域性的window物件。因為this關鍵字沒有處於一個宣告的物件內部,預設指向全域性window物件。這一點可能對於大部分初學者來說有點難以理解。當讀完這篇文章,你就會豁然開朗。 注意:如果在strcit模式下,上面的例子中thisundefined

宣告的物件 (Declared Object)

this關鍵字在一個宣告物件內部使用,其值會被繫結到呼叫該this的函式的最近的父物件。我們用例子來說明這個問題:

var person = {
  first: 'John',
  last: 'Smith',  
  full: function() {
    console.log(this.first + ' ' + this.last);
  }
};
person.full();
// 輸出 'John Smith'
複製程式碼

在被宣告的物件person中的full函式裡面使用了this, 那麼呼叫thisfull函式的最近的父物件就是person, 因此,this指向person

為了更好的描述this實際上是指向person物件的,你可以拷貝如下程式碼到瀏覽器控制檯,將this列印出來。

var person = {
  first: 'John',
  last: 'Smith',  
  full: function() {
    console.log(this);
  }
};
person.full();
// 輸出 Object {first: "John", last: "Smith", full: function}
複製程式碼

我們接下來看一個更復雜的例子:

var person = {
  first: 'John',
  last: 'Smith',
  full: function() {
    console.log(this.first + ' ' + this.last);
  },
  personTwo: {
    first: 'Allison',
    last: 'Jones',
    full: function() {
      console.log(this.first + ' ' + this.last);
    }
  }
};
複製程式碼

在這裡我們有巢狀的物件,在這個時候,this分別指向誰呢? 我們將其列印出來看一看:

person.full();
// 輸出 'John Smith'
person.personTwo.full();
// 輸出 'Allison Jones'
複製程式碼

你會發現滿足我們前面所描述的規則:其值會被繫結到呼叫this的函式的最近的父物件。

new關鍵字

當使用new關鍵字構建一個新的物件,this會繫結到這個新物件。我們來看一個例子:

function Car(make, model) {
  this.make = make;
  this.model = model;
};
複製程式碼

根據第一條規則,你可能會推斷this指向全域性物件。但是如果我們使用new關鍵字來宣告一個新的變數,Car函式中的this將會繫結一個新的空物件,然後初始化this.makethis.model的值。

var myCar = new Car('Ford', 'Escape');
console.log(myCar);
// 輸出 Car {make: "Ford", model: "Escape"}
複製程式碼

call, bind, 和apply

我們可以顯示的在call()bind()apply()中設定this的繫結物件。這三個函式很類似,但是我們需要注意它們微小的區別。

我們來看一個例子:

function add(c, d) {
  console.log(this.a + this.b + c + d);
}
add(3,4);
// 輸出 NaN
複製程式碼

add函式輸出NaN, 因為this.athis.b未定義。

現在我們引入物件,並且使用call()apply()來呼叫:

function add(c, d) {
  console.log(this.a + this.b + c + d);
}
var ten = {a: 1, b: 2};
add.call(ten, 3, 4);
// 輸出 10
add.apply(ten, [3,4]);
// 輸出 10
複製程式碼

當我們使用add.call(),第一個引數是this需要繫結的物件,剩下的是add函式本來的引數。 因此,this.a指向ten.a,this.b指向ten.badd.apply()也類似,除了第二個引數是一個陣列,用於儲存add函式的引數。

bind()函式和call()類似,但是bind()函式不會立即被呼叫。bind()函式會返回一個函式,並且將this繫結好。接下來我們來用例子來幫助理解bind()函式的應用場景:

var small = {
  a: 1,
  go: function(b,c,d){
    console.log(this.a+b+c+d);
  }
}
var large = {
  a: 100
}
複製程式碼

執行:

small.go(2, 3, 4);
// 輸出 10
複製程式碼

如果我們想使用large.a的值,而不是small.a呢? 我們可以使用call/apply:

small.go.call(large, 2, 3, 4);
// 輸出 109
複製程式碼

但是,如果我們現在還不知道這三個引數應該傳入什麼值,應該怎麼辦呢? 我們可以使用bind:

var bindTest = small.go.bind(large, 2);
複製程式碼

如果我們將bindTest在控制檯下列印出來,我們會看到:

console.log(bindTest);
// 輸出 function (b,c,d){console.log(this.a+b+c+d);}
複製程式碼

注意:該函式已經將this繫結到large物件,並且傳入了第一個引數b。所以,我們接下來是需要傳入餘下的引數即可:

bindTest(3, 4);
// 輸出 109
複製程式碼

箭頭函式(->)

因為需要很大的篇幅,我們會專門寫一篇部落格來介紹。

結論

當你讀完這篇部落格,你應該可以理解大多數情況下this指向的物件。 接下來我們來總結一下:

  1. this的值通常是由當前函式的執行環境所決定;
  2. 在全域性作用域,this指向全域性物件 (window物件);
  3. 當使用new關鍵字宣告,this指向新建物件;
  4. 我們可以使用call(), bind(), apply()來設定this
  5. 箭頭函式不會繫結this

關於Fundebug

Fundebug專注於JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了6億+錯誤事件,得到了Google、360、金山軟體等眾多知名使用者的認可。歡迎免費試用!

JavaScript初學者必看“this”

版權宣告

轉載時請註明作者Fundebug以及本文地址:
blog.fundebug.com/2017/05/17/…

相關文章