翻譯:道奇
做者:Dmitri Pavlutin
原文:How to Handle Easily 'this' in JavaScriptjavascript
我喜歡JavaScript改變執行上下文的能力,也被稱爲this
。
舉個例子,能夠在類數組的對象上使用數組方法:java
const reduce = Array.prototype.reduce;
function sumArgs() {
return reduce.call(arguments, (sum, value) => {
return sum += value;
});
}
sumArgs(1, 2, 3); // => 6
複製代碼
另外一方面,this
關鍵字很難掌握。你可能常常在查找爲何this
獲得的值是不正確的。下面的章節會告訴你一些簡單的方法,如何將this
綁定到所需的值上。git
在開始前,須要一個幫助函數execute(func)
,它以參數的形式執行函數:github
function execute(func) {
return func();
}
execute(function() { return 10 }); // => 10
複製代碼
如今繼續理解圍繞this
錯誤的本質:方法分離。數組
Person
類包含字段firstName
和lastName
,另外還有個返回person
全名的getFullName()
方法。
Person
的一種實現多是:瀏覽器
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = function() {
this === agent; // => true
return `${this.firstName} ${this.lastName}`;
}
}
const agent = new Person('John', 'Smith');
agent.getFullName(); // => 'John Smith'
複製代碼
能夠看到Person
函數被看成構造函數調用:new Person('Jonh','Smith')
,在Person
函數內部,this
是個新建的實例。bash
agent.getFullName
返回person
的全名:'John Smith'。如你所料,getFullName()
方法內部的this
等於agent
。函數
若是agent.getFullName
方法由幫助函數Execute
執行會發生什麼呢:優化
execute(agent.getFullName); // => 'undefined undefined'
複製代碼
執行結果會不對: 'undefined undefined',緣由是this
的值不正確。ui
在getFullName()
內部this
的值指向的是全局對象(在瀏覽器環境下就是window
對象)。由於this
等於window
,求值表達式${window.firstName} ${window.lastName}
的結果是'undefined undefined'。
這是由於當調用execute(agent.getFullName)
函數時,函數與對象分離了。基本上就至關於只是一個普通的函數調用(而不是方法調用):
execute(agent.getFullName); // => 'undefined undefined'
//等於:
const getFullNameSeparated = agent.getFullName;
execute(getFullNameSeparated); // => 'undefined undefined'
複製代碼
這就是爲何我說 「函數與對象分離」。當方法分離後再執行,它就和它所在的源對象沒有關聯了。
若是要確保方法內的this
指向準確的對象,就必須:
agent.getFullName()
this
靜態的綁定到所在的對象(使用箭頭函數,.bind()
方法等等)方法分離問題,致使this
的值不對,會呈現多種不一樣的狀況:
設置回調時
// `this` 在 `methodHandler()` 內部是全局對象
setTimeout(object.handlerMethod, 1000);
複製代碼
設置事件處理器時
// React: `this` 在 `methodHandler()`內部是全局對象
<button onClick={object.handlerMethod}>
Click me
</button>
複製代碼
儘管方法和對象分離了,仍是有一些能夠將this
指向所需對象的好用的方法。
最簡單的方式就是使用額外的變量self
將this
指向類的實例:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
const self = this;
this.getFullName = function() {
self === agent; // => true
return `${self.firstName} ${self.lastName}`;
}
}
const agent = new Person('John', 'Smith');
agent.getFullName(); // => 'John Smith'
execute(agent.getFullName); // => 'John Smith'
複製代碼
getFullName()
經過self
變量,將方法手動綁定到this
上。
如今調用execute(agent.getFullName)
代碼將正常執行,返回 'John Smith',由於getFullName()
方法老是獲得正確this值。
若是不經過額外的變量有沒有方法靜態的綁定this
呢?是的,箭頭函數就是作這個事情的。
使用箭頭函數重構一下person
:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = () => `${this.firstName} ${this.lastName}`;
}
const agent = new Person('John', 'Smith');
agent.getFullName(); // => 'John Smith'
execute(agent.getFullName); // => 'John Smith'
複製代碼
箭頭函數只是從詞法上綁定this
,簡單的說,它使用外部函數定義this
的值。
當你要使用外部函數的上下文時,我推薦在全部的狀況下都使用箭頭函數。
如今咱們再往前一步,使用ES2015
的類來重構Person
。
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
}
const agent = new Person('John', 'Smith');
agent.getFullName(); // => 'John Smith'
execute(agent.getFullName); // => 'undefined undefined'
複製代碼
很不幸,即便使用最新的類語法,execute(agent.getFullName)
仍是返回'undefined undefined'
。在類裏,使用額外變量self
或箭頭函數來固定this
的值是行不通的。
可是有個小技巧是使用bind()方法在構造函數裏綁定方法的上下文:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = this.getFullName.bind(this);
}
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
}
const agent = new Person('John', 'Smith');
agent.getFullName(); // => 'John Smith'
execute(agent.getFullName); // => 'John Smith'
複製代碼
構造函數內部this.getFullName = this.getFullName.bind(this)
這行語句將getFullName()
方法綁定到了類實例上。
execute(agent.getFullName)
按預期的那樣執行,返回'John Smith'。
上面的方法經過手動綁定上下文須要寫一行樣板代碼(this.getFullName = this.getFullName.bind(this);)
。幸運的是,還有優化的空間。
可使用JavaScript的類字段的建議容許定義胖箭頭方法:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName = () => {
return `${this.firstName} ${this.lastName}`;
}
}
const agent = new Person('John', 'Smith');
agent.getFullName(); // => 'John Smith'
execute(agent.getFullName); // => 'John Smith'
複製代碼
就算將方法和它的對象分離,胖箭頭方法getFullName = () => { ... }
綁定仍是會綁定到類實例上。這種是在類中綁定this
最有效且最簡潔的方式了。
方法和它的對象分離致使了不少關於this
的誤解,須要意識到這種影響。
爲了靜態綁定this
,能夠手動的使用額外的變量self
關聯正確的上下文對象。可是,更好的選擇是使用箭頭函數,它的語法上是自然設計綁定this
的。
在類中,能夠在構造函數中使用bind()
方法手動地綁定類方法。
若是你不想寫樣板代碼,新的JavaScript的類字段建議增長了胖箭頭方法能夠自動的將this
綁定到類實例上。