做者:Dmitri Pavlutin翻譯:瘋狂的技術宅javascript
原文:https://dmitripavlutin.com/fi...前端
未經容許嚴禁轉載java
我喜歡 JavaScript 中可以更改函數執行上下文(也稱爲 this
)的特性。git
例如,你能夠在相似數組的對象上使用數組方法:程序員
const reduce = Array.prototype.reduce; function sumArgs() { return reduce.call(arguments, (sum, value) => { return sum += value; }); } sumArgs(1, 2, 3); // => 6
可是從另外一方面來講,this
關鍵字很難掌握。github
你可能會常常去檢查 this
的值不正確的緣由。如下各節將會教給你一些把 this
綁定到所需的值簡單的方法。面試
在開始以前,我須要一個輔助函數 execute(func)
。它只是用來執行做爲參數的函數:segmentfault
function execute(func) { return func(); } execute(function() { return 10 }); // => 10
如今,讓咱們繼續瞭解圍繞 this
的錯誤的本質:方法分離。數組
Person
類包含字段 firstName
和 lastName
。另外,它還有 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('John','Smith')
。在 Person
函數內部建立新的實例。
agent.getFullName()
返回 person
的全名:'John Smith'
。不出所料,getFullName()
方法中的 this
等同於 agent
。
若是幫助函數執行 help.getFullName
方法將會發生什麼:
execute(agent.getFullName); // => 'undefined undefined'
執行結果不正確:'undefined undefined'
。這個問題是由 this
值不正確引發的。
如今,在方法 getFullName()
中,this
的值是全局對象(瀏覽器環境中的 window)。假設 this
等於 window
,則對 ${window.firstName} ${window.lastName}
的評估爲 undefined undefined
。
發生這種狀況的緣由是在調用 execute(agent.getFullName)
時該方法已與對象分離。基本上只是發生在常規函數調用上(而不是方法調用):
execute(agent.getFullName); // => 'undefined undefined' // is equivalent to: const getFullNameSeparated = agent.getFullName; execute(getFullNameSeparated); // => 'undefined undefined'
這種效果就是我所說的與對象分離的方法。當方法被分離並隨後執行時,它與其原始對象沒有任何關係。
爲了確保方法中的 this
指向正確的對象,你必須:
agent.getFullName()
this
靜態綁定到包含的對象(使用箭頭函數,.bind()
方法等)在方法分離問題中,返回的 this
不正確,如下面不一樣的形式出現:
// `this` inside `methodHandler()` is the global object setTimeout(object.handlerMethod, 1000);
// React: `this` inside `methodHandler()` is the global object <button onClick={object.handlerMethod}> Click me </button>
讓咱們繼續瞭解一些有用的方法,來解決即便方法與對象是分開的,也能使其始終指向所需對象的問題。
使 this
指向類實例的最簡單方法是使用附加變量 self
:
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'
。
上述使用手動上下文綁定的方法須要樣板代碼。幸運的是,仍有改進的空間。
你能夠用 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
綁定到類實例。