原文:What is 「this」 in JavaScript?javascript
若是你曾使用JavaScript庫作過開發,那麼你可能已經注意到一個名爲 this
的特定關鍵字。雖然 this
在JavaScript中很是常見,可是徹底理解this
關鍵字的原理以及在代碼中如何使用對至關一部分的開發者來講着實不易。在這篇文章中,我將幫你深刻理解 this
及其工做機制。java
在開始以前,請確保已安裝 Node。而後,打開命令行終端並運行 node
命令。node
this的工做機制理解起來並非很容易。咱們經過將 this
置於不一樣環境下,分別來理解 this
是如何工做的。首先看一下 global
環境。git
在全局環境中, this
至關於全局對象 global
。github
> this === global
true
複製代碼
但這隻在 node
中才有效。若是咱們將相同的代碼放在js文件中運行,獲得的輸出爲false。app
若是想測試效果,能夠建立一個名爲 index.js
的文件,包含如下代碼:函數
console.log(this === global);
複製代碼
而後使用 node
命令運行此文件:測試
$ node index.js
false
複製代碼
緣由是在JavaScript文件中, this等同於 module.exports
而不是 global
。ui
函數中的 this
值一般是由函數的調用方來定義。所以,每次執行函數,函數內的 this
值可能都不同。this
在 index.js
文件中,編寫一個很是簡單的函數,來檢查 this
是否等於global
對象。
function rajat() {
console.log(this === global)
}
rajat()
複製代碼
若是咱們使用 node
運行此代碼,獲得的輸出爲 true 。若是在文件的頂部添加 "use strict"
,並再次運行它,將會獲得一個 false輸出,由於如今的 this值爲 undefined 。
爲了進一步解釋這一點,讓咱們建立一個簡單的函數來定義超級英雄的真實姓名和英雄名字。
function Hero(heroName, realName) {
this.realName = realName;
this.heroName = heroName;
}
const superman= Hero("Superman", "Clark Kent");
console.log(superman);
複製代碼
請注意,這個函數並非以嚴格模式編寫的。 node
運行此代碼,並不會獲得咱們預期的「Superman」和「Clark Kent」,但它只會給咱們一個 undefined 。
背後的緣由是,因爲函數不是以嚴格模式編寫的, this
指向global對象。
若是在嚴格模式下運行此代碼,咱們會收到報錯,由於JavaScript不容許將屬性 realName
和 heroName
賦給 undefined
。其實這是一件好事,由於它阻止咱們建立全局變量。
另外,以大寫形式書寫函數名意味着咱們須要將它視爲構造函數,使用 new
運算符來調用。用下面的代碼替換上面代碼段的最後兩行:
const superman = new Hero("Superman", "Clark Kent");
console.log(superman);
複製代碼
再次運行 node index.js
命令,如今會獲得你預期的輸出。
JavaScript沒有特定的構造函數。咱們所能作的就是使用 new
運算符將函數調用轉換爲構造函數調用,如上一節所示。
構造函數被調用時,會建立一個新對象並將其設置爲函數的 this
參數。構造函數會隱式的返回這個對象,除非咱們明確的返回了另一個對象。
在 hero
函數內部添加下面的 return
語句:
return {
heroName: "Batman",
realName: "Bruce Wayne",
};
複製代碼
若是咱們再次運行 node
命令,咱們會看到 上面的
return語句覆蓋了構造函數調用。
return語句不會覆蓋構造函數調用的惟一情形是,return語句返回的不是一個對象。在這種狀況下,對象將包含原始值。
當函數做爲對象的方法被調用時, this指向的是該對象,也稱爲函數調用的接收器(receiver)。
假設 hero對象有一個 dialogue方法 ,那麼 dialogue中的 this值指向 hero自己。此時的 hero也被稱爲 dialogue方法調用的接收者。
const hero = {
heroName: "Batman",
dialogue() {
console.log(`I am ${this.heroName}!`);
}
};
hero.dialogue();
複製代碼
這個例子很是簡單,但在實際狀況中,咱們的方法很難跟蹤接收器。在 index.js的末尾添加如下代碼段。
const saying = hero.dialogue;
saying();
複製代碼
若是我將 dialogue的引用存儲在另外一個變量中,並將該變量做爲函數調用。 node運行代碼 , this將返回 undefined ,由於該方法已經丟失了接收器。 this此時指向 global ,而不是 hero 。
當咱們將一個方法做爲回調傳遞給另外一個方法時,一般會丟失接收器。咱們能夠經過添加包裝函數或使用 bind方法將 this與特定對象綁定來解決這個問題。
雖然函數的 this值是隱式設置的,咱們在調用函數時也可使用 call()和 apply()明確設置 this參數。
咱們重構一下前面章節的代碼片斷,以下所示:
function dialogue () {
console.log (`I am ${this.heroName}`);
}
const hero = {
heroName: 'Batman',
};
複製代碼
若是要將 hero對象做爲 dialogue函數的接收器,咱們能夠這樣使用 call()或 apply() :
dialogue.call(hero)
// or
dialogue.apply(hero)
複製代碼
若是你是在非嚴格模式下使用 call或 apply ,JavaScript引擎會忽略傳遞給 call或 apply的 null或 undefined (譯者注:被替換爲global)。這也是爲何建議始終以嚴格模式編寫代碼的緣由之一。
當咱們將一個方法做爲回調函數傳遞給另外一個函數時,老是存在丟失方法的原有接收器的風險,使得 this參數指向全局對象。
bind()方法能夠將 this參數固定的綁定到一個值上。下面的代碼片斷, bind會建立一個新的 dialogue函數,並將 this值設置爲 hero 。(譯者注:bind()方法會建立一個新函數,稱爲綁定函數-bound function-BF,當調用這個綁定函數時,綁定函數會以建立它時傳入 bind()方法的第一個參數做爲 this,傳入 bind() 方法的第二個以及之後的參數加上綁定函數運行時自己的參數按照順序做爲原函數的參數來調用原函數。)
const hero = {
heroName: "Batman",
dialogue() {
console.log(`I am ${this.heroName}`);
}
};
setTimeOut(hero.dialogue.bind(hero), 1000);
複製代碼
這樣的話,即便使用 call或 apply方法也沒法改變 this的值 。
箭頭函數內的 this與其餘類型的JavaScript函數有很大的不一樣。An arrow function uses the this value from its enclosing execution context, since it does have one of its own.
箭頭函數會永久地捕獲 this值,阻止 apply或 call後續更改它。
爲了解釋箭頭函數中的 this是如何工做的,咱們來寫一個箭頭函數:
const batman = this;
const bruce = () => {
console.log(this === batman);
};
bruce();
複製代碼
這裏,咱們將 this值存儲在變量中,而後將該值與箭頭函數內的 this值進行比較。在終端中運行 node index.js,輸出應該爲 true 。
箭頭函數內的 this值沒法明確設置。此外,使用 call 、 apply或 bind等方法給 this傳值,箭頭函數會忽略。箭頭函數引用的是箭頭函數在建立時設置的 this值。(譯者注:箭頭函數中沒有this綁定,必須經過查找做用域鏈來決定它的值,若是箭頭函數被非箭頭函數包裹,那麼this值由外圍最近一層非箭頭函數決定,不然爲undefined。)
箭頭函數也不能用做構造函數。所以,咱們也不能在箭頭函數內給 this設置屬性。
那麼箭頭函數對 this 能夠作什麼呢?
箭頭函數可使咱們在回調函數中訪問 this 。經過下面的 counter對象來看看如何作到的:
const counter = {
count: 0,
increase() {
setInterval(function() {
console.log(++this.count);
}, 1000);
}
}
counter.increase();
複製代碼
使用 node index.js運行此代碼,只會獲得一個 NaN的列表。這是由於 this.count已經不是指向 counter對象了。它實際上指向的爲 global對象。
若是想讓計數器正常工做,可使用箭頭函數重寫它。
const counter = {
count: 0,
increase () {
setInterval (() => {
console.log (++this.count);
}, 1000);
},
};
counter.increase();
複製代碼
回調函數使用 this與 increase方法綁定, counter如今能夠正常工做了。
注意 :不要將 ++this.count 寫成 this.count + 1。後者只會增長count的值一次,每次迭代都會返回相同的值。
類是JavaScript應用程序中最重要的一部分。讓咱們看看類中的this有何不一樣。
類一般包含一個 constructor , this能夠指向任何新建立的對象。
不過在做爲方法時,若是該方法做爲普通函數被調用, this也能夠指向任何其餘值。與方法同樣,類也可能失去對接收器的跟蹤。
咱們將以前建立的 Hero函數改造爲類。該類包含一個構造函數和一個 dialogue()方法。最後,建立一個類的實例並調用 dialogue方法。
class Hero {
constructor(heroName) {
this.heroName = heroName;
}
dialogue() {
console.log(`I am ${this.heroName}`)
}
}
const batman = new Hero("Batman");
batman.dialogue();
複製代碼
構造函數裏的 this指向新建立的 類實例。當咱們調用 batman.dialogue()時, dialogue()做爲方法被調用, batman是它的接收器。
可是若是咱們將 dialogue()方法的引用存儲起來,並稍後將其做爲函數調用,咱們會丟失該方法的接收器,此時 this參數指向 undefined 。
const say = batman.dialogue;
say();
複製代碼
出現錯誤的緣由是JavaScript 類是隱式的運行在嚴格模式下的。咱們是在沒有任何自動綁定的狀況下調用 say()函數的。要解決這個問題,咱們須要手動使用 bind()將 dialogue()函數與 batman綁定在一塊兒。
const say = batman.dialogue.bind(batman);
say();
複製代碼
咱們也能夠在 構造函數方法中作這個綁定。
咱們須要在JavaScript中使用 this ,就像咱們須要在英語中使用代詞同樣。以這兩句話爲例:
咱們可使用代詞將這兩個句子組合在一塊兒,因此這兩句話如今成了:
Rajat loves DC Comics, and he also loves Marvel Comics
這個簡短的語法課完美地解釋了 this
在JavaScript中的重要性。就像代詞 he將兩個句子鏈接在一塊兒同樣, this
能夠做爲再次引用相同內容的捷徑。
但願這篇文章能夠幫助你解答JavaScript中有關 this
的困惑,輕鬆駕馭這個簡單但很是重要的關鍵字。
《IVWEB 技術週刊》 震撼上線了,關注公衆號:IVWEB社區,每週定時推送優質文章。