在 JavaScript 中輕鬆處理 「this」

做者: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 的錯誤的本質:方法分離。數組

1. 方法分離問題

Person 類包含字段 firstNamelastName。另外,它還有 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 指向正確的對象,你必須:

  1. 以屬性訪問器的形式執行該方法:agent.getFullName()
  2. 或將 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>

讓咱們繼續瞭解一些有用的方法,來解決即便方法與對象是分開的,也能使其始終指向所需對象的問題。

2. 關閉上下文

使 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 值,因此可以正常工做。

3. 使用箭頭功能對 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 值。

我建議在全部須要使用外部函數上下文的狀況下都使用箭頭函數。

4.綁定上下文

讓咱們再向前邁出一步,並使用 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'

5. 胖箭頭方法

上述使用手動上下文綁定的方法須要樣板代碼。幸運的是,仍有改進的空間。

你能夠用 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 綁定到類實例。


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索