this
是一個令無數 JavaScript 編程者又愛又恨的知識點。它的重要性毋庸置疑,然而真正想掌握它卻並不是易事。但願本文能夠幫助你們理解 this
。javascript
JavaScript 引擎在查找 this
時不會經過原型鏈一層一層的查找,由於 this
徹底是在函數調用時才能夠肯定的,讓咱們來看下面幾種函數調用的形式。前端
普通的函數調用,這是咱們使用較多的一種, foo
是以單獨的變量出現而不是屬性。其中的 this
指向全局對象。java
function foo() {
console.log(this)
}
foo() // Window
複製代碼
函數做爲對象的方法調用,會經過 obj.func
或者 obj[func]
的形式調用。其中的 this
指向調用它的對象。node
const obj = {
name: 'lxfriday',
getName(){
console.log(this.name)
}
}
obj.getName() // lxfriday
複製代碼
經過 new Constructor()
的形式調用,其 this
會指向新生成的對象。面試
function Person(name){
this.name = name
}
const person = new Person('lxfriday')
console.log(person.name) // lxfriday
複製代碼
經過 foo.apply(thisObj)
或者 foo.call(thisObj)
的形式調用,其中的 this
指向 thisObj
。若是 thisObj
是 null
或者 undefined
,其中的 this
會指向全局上下文 Window
(在瀏覽器中)。編程
掌握以上的幾種函數調用形式就基本能夠覆蓋開發中遇到的常見問題了,下面我翻譯了一篇文章,幫助你更深刻的理解 this
。數組
本文接下來的內容翻譯自 blog.bitsrc.io/what-is-thi…,做者 Rajat S,內容有刪改,標題有改動。瀏覽器
若是你已經使用過一些 JavaScript 庫,你必定會注意到一個特殊的關鍵字 this
。bash
this
在 JavaScript 中很常見,可是有不少開發人員花了不少時間來徹底理解 this
關鍵字的確切功能以及在代碼中何處使用。閉包
在這篇文章中,我將幫助您深刻了解 this
其機制。
在深刻了解以前,請確保已在系統上安裝了 Node 。而後,打開命令終端並運行 node 命令。
this
的工做機制並不容易理解。爲了理解 this
是如何工做的,咱們將探索不一樣環境中的 this
。首先咱們從全局上下文開始。
在全局層面中,this
等同於全局對象,在 Node repl(交互式命令行) 環境中叫 global
。
$ node
> this === global
true
複製代碼
但上述狀況只出如今 Node repl 環境中,若是咱們在 JS 文件中跑相同的代碼,咱們將會獲得不一樣的答案。
爲了測試,咱們建立一個 index.js
的文件,並添加下面的代碼:
console.log(this === global);
複製代碼
而後經過 node
命令運行:
$ node index.js
false
複製代碼
出現上面狀況的緣由是在 JS 文件中, this
指向 module.exports
,並非指向 global
。
Function Invocation Pattern
在函數中 this
的指向取決於函數的調用形式。因此,函數每次執行的時候,可能擁有不一樣的 this
指向。
在 index.js
文件中,編寫一個很是簡單的函數來檢查 this
是否指向全局對象:
function fat() {
console.log(this === global)
}
fat()
複製代碼
若是咱們在 Node repl 環境執行上面的代碼,將會獲得 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
引用了全局對象。
若是咱們在嚴格模式下運行這段代碼,會由於 JavaScript 不容許給 undefined
增長屬性而出現錯誤。這其實是一件好事,由於它阻止咱們建立全局變量。
最後,以大寫形式編寫函數的名稱意味着咱們須要使用 new
運算符將其做爲構造函數來調用。將上面的代碼片斷的最後兩行替換爲:
const superman = new Hero("Superman", "Clark Kent");
console.log(superman);
複製代碼
再次運行 node index.js
命令,您如今將得到預期的輸出。
Constructor Pattern
JavaScript 沒有任何特殊的構造函數。咱們所能作的就是使用 new
運算符將函數調用轉換爲構造函數調用,如上一節所示。
進行構造函數調用時,將建立一個新對象並將其設置爲函數的 this
參數。而後,從函數隱式返回該對象,除非咱們有另外一個要顯式返回的對象。
在 hero
函數內部編寫如下 return
語句:
return {
heroName: "Batman",
realName: "Bruce Wayne",
};
複製代碼
若是如今運行 node
命令,咱們將看到 return
語句將覆蓋構造函數調用。
當 return
語句嘗試返回不是對象的任何東西時,將隱式返回 this
。
Method Invocation Pattern
當將函數做爲對象的方法調用時,this
指向該對象,而後將該對象稱爲該函數調用的接收者。
在下面代碼中,有一個 dialogue
方法在 hero
對象內。經過 hero.dialogue()
形式調用時,dialogue
中的 this
就會指向 hero
自己。這裏,hero
就是 dialogue
方法調用的接收者。
const hero = {
heroName: "Batman",
dialogue() {
console.log(`I am ${this.heroName}!`);
}
};
hero.dialogue();
複製代碼
上面的代碼很是簡單,可是實際開發時有可能方法調用的接收者並非原對象。看下面的代碼:
const saying = hero.dialogue();
saying();
複製代碼
這裏,咱們把方法賦值給一個變量,而後執行這個變量指向的函數,你會發現 this
的值是 undefined
。這是由於 dialogue
方法已經沒法跟蹤原來的接收者對象,函數如今指向的是全局對象。
當咱們將一個方法做爲回調傳遞給另外一個方法時,一般會發生接收器的丟失。咱們能夠經過添加包裝函數或使用 bind
方法將 this
綁定到特定對象來解決此問題。
Apply Pattern
儘管函數的 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)
複製代碼
須要注意的是,在非嚴格模式下,若是傳遞 null
或者 undefined
給 call
、 apply
做爲上下文,將會致使 this
指向全局對象。
function dialogue() {
console.log('this', this)
}
const hero = {
heroName: 'Batman',
}
console.log(dialogue.call(null))
複製代碼
上述代碼,在嚴格模式下輸出 null
,非嚴格模式下輸出全局對象。
當咱們將一個方法做爲回調傳遞給另外一個函數時,始終存在丟失該方法的預期接收者的風險,致使將 this
參數設置爲全局對象。
bind()
方法容許咱們將 this
參數永久綁定到函數。所以,在下面的代碼片斷中,bind
將建立一個新 dialogue
函數並將其 this
值設置爲 hero
。
const hero = {
heroName: "Batman",
dialogue() {
console.log(`I am ${this.heroName}`);
}
};
// 1s 後打印:I am Batman
setTimeout(hero.dialogue.bind(hero), 1000);
複製代碼
注意:對於用 bind
綁定 this
以後新生成的函數,使用 call
或者 apply
方法沒法更改這個新函數的 this
。
箭頭函數和普通函數有很大的不一樣,引用阮一峯 ES6入門第六章中的介紹:
this
對象,就是定義時所在的對象,而不是使用時所在的對象;new
命令,不然會拋出一個錯誤;arguments
對象,該對象在函數體內不存在。若是要用,能夠用 rest
參數代替;yield
命令,所以箭頭函數不能用做 Generator
函數;上面四點中,第一點尤爲值得注意。this
對象的指向是可變的,可是在箭頭函數中,它是固定的,它只指向箭頭函數定義時的外層 this
,箭頭函數沒有本身的 this
,全部綁定 this
的操做,如 call
apply
bind
等,對箭頭函數中的 this
綁定都是無效的。
讓們看下面的代碼:
const batman = this;
const bruce = () => {
console.log(this === batman);
};
bruce();
複製代碼
在這裏,咱們將 this
的值存儲在變量中,而後將該值與箭頭函數內部的 this
值進行比較。node index.js
執行時將會輸出 true
。
那箭頭函數中的 this
能夠作哪些事情呢?
箭頭函數能夠幫助咱們在回調中訪問 this
。看一下我在下面寫的 counter
對象:
const counter = {
count: 0,
increase() {
setInterval(function() {
console.log(++this.count);
}, 1000);
}
}
counter.increase();
複製代碼
運行上面的代碼,會打印 NaN
。這是由於 this.count
沒有指向 counter
對象。它實際上指向全局對象。
要使此計數器工做,能夠用箭頭函數重寫,下面代碼將會正常運行:
const counter = {
count: 0,
increase () {
setInterval (() => {
console.log (++this.count);
}, 1000);
},
};
counter.increase ();
複製代碼
類是全部 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();
複製代碼
constructor
中的 this
指向新建立的類實例。batman.dialogue()
調用時,咱們將 dialogue()
做爲 batman
接收器的方法調用。
可是,若是咱們存儲對 dialogue()
方法的引用,而後將其做爲函數調用,則咱們將再次失去方法的接收者,而 this
如今指向 undefined
。
爲何是指向 undefined
呢?這是由於 JavaScript 類內部隱式以嚴格模式運行。咱們將 say()
做爲一個函數調用而沒有進行綁定。因此咱們要手動的綁定。
const say = batman.dialogue.bind(batman);
say();
複製代碼
固然,咱們也能夠在構造函數內部綁定:
class Hero {
constructor(heroName) {
this.heroName = heroName
this.dialogue = this.dialogue.bind(this)
}
dialogue() {
console.log(`I am ${this.heroName}`)
}
}
複製代碼
call
和 apply
的模擬實現大同小異,注意 apply
的參數是一個數組,綁定 this
都採用的是對象調用方法的形式。
Function.prototype.call = function(thisObj) {
thisObj = thisObj || window
const funcName = Symbol('func')
const that = this // func
thisObj[funcName] = that
const result = thisObj[funcName](...arguments)
delete thisObj[funcName]
return result
}
Function.prototype.apply = function(thisObj) {
thisObj = thisObj || window
const funcName = Symbol('func')
const that = this // func
const args = arguments[1] || []
thisObj[funcName] = that
const result = thisObj[funcName](...[thisObj, ...args])
delete thisObj[funcName]
return result
}
Function.prototype.bind = function(thisObj) {
thisObj = thisObj || window
const that = this // func
const outerArgs = [...arguments].slice(1)
return function(...innerArgs) {
return that.apply(thisObj, outerArgs.concat(innerArgs))
}
}
複製代碼
往期精彩:
關注公衆號能夠看更多哦。
感謝閱讀,歡迎關注個人公衆號 雲影 sky,帶你解讀前端技術,掌握最本質的技能。關注公衆號能夠拉你進討論羣,有任何問題都會回覆。