JS 中幾種輕鬆處理』this』指向方式

  • 2019 年 10 月 11 日
  • 筆記

我喜歡在JS中更改函數執行上下文的指向,也稱為 this 指向。

例如,咱們可以在類數組對象上使用數組方法:

const reduce = Array.prototype.reduce;    function sumArgs() {     return reduce.call(arguments, (sum, value) => {           return sum += value;    });  }     sumArgs(1, 2, 3); // => 6   ```    另一方面,`this` 很難把握。    咱們經常會發現自己用的 `this` 指向不正確。下面的教你如何簡單地將 `this` 綁定到所需的值。    在開始之前,我需要一個輔助函數`execute(func)`,它僅執行作為參數提供的函數。    ```JavaScript  function execute(func) {      return func();  }     execute(function() { return 10 }); // => 10   ```    現在,繼續理解圍繞`this`錯誤的本質:方法分離。    ## 1.方法分離問題  假設有一個類`Person`包含欄位`firstName`和`lastName`。此外,它還有一個方法`getFullName()`,該方法返回此人的全名。如下所示:    ```JavaScript  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('前端', '小智');   agent.getFullName(); // => '前端 小智'   ```    可以看到`Person`函數作為構造函數被調用:`new Person('前端', '小智')`。 函數內部的 `this` 表示新創建的實例。    `getfullname()`返回此人的全名:`'前端 小智'`。正如預期的那樣,`getFullName()`方法內的 `this` 等於`agent`。    如果輔助函數執行`agent.getFullName`方法會發生什麼:    ```JavaScript   execute(agent.getFullName); // => 'undefined undefined'   ```    執行結果不正確:`'undefined undefined'`,這是 `this` 指向不正確導致的問題。    現在在`getFullName()` 方法中,`this`的值是全局對象(瀏覽器環境中的 `window` )。 `this` 等於 `window`,`${window.firstName} ${window.lastName}` 執行結果是 `'undefined undefined'`。    發生這種情況是因為在調用`execute(agent.getFullName)`時該方法與對象分離。 基本上發生的只是常規函數調用(不是方法調用):    ```JavaScript   execute(agent.getFullName); // => 'undefined undefined'    // 等價於:    const getFullNameSeparated = agent.getFullName;   execute(getFullNameSeparated); // => 'undefined undefined'   ```    這個就是所謂的方法從它的對象中分離出來,當方法被分離,然後執行時,`this` 與原始對象沒有連接。    為了確保方法內部的`this`指向正確的對象,必須這樣做    1. 以屬性訪問器的形式執行方法:`agent.getFullName()`  2. 或者靜態地將`this`綁定到包含的對象(使用箭頭函數、`.bind()`方法等)    方法分離問題,以及由此導致`this`指向不正確,一般會在下面的幾種情況中出現:    **回調**    ```JavaScript  // `methodHandler()`中的`this`是全局對象  setTimeout(object.handlerMethod, 1000);

在設置事件處理程式時

// React: `methodHandler()`中的`this`是全局對象  <button onClick={object.handlerMethod}>    Click me  </button>

接著介紹一些有用的方法,即如果方法與對象分離,如何使this指向所需的對象。

2. 關閉上下文

保持this指向類實例的最簡單方法是使用一個額外的變數self:

```JavaScript   function Person(firstName, lastName) {       this.firstName = firstName;     this.lastName = lastName;  const self = this;  this.getFullName = function() {        self === agent; // => true            return <code>${self.firstName} ${self.lastName}</code>;     }   }  const agent = new Person('前端', '小智');  agent.getFullName();        // => '前端 小智'    execute(agent.getFullName); // => '前端 小智'    ```

getFullName()靜態地關閉self變數,有效地對this進行手動綁定。

現在,當調用execute(agent.getFullName)時,一切工作正常,因為getFullName()方法內 this 總是指向正確的值。

3.使用箭頭函數

有沒有辦法在沒有附加變數的情況下靜態綁定this? 是的,這正是箭頭函數的作用。

使用箭頭函數重構Person:

```JavaScript   function Person(firstName, lastName) {       this.firstName = firstName;     this.lastName = lastName;  this.getFullName = () => <code>${this.firstName} ${this.lastName}</code>;      }  const agent = new Person('前端', '小智');  agent.getFullName();        // => '前端 小智'    execute(agent.getFullName); // => '前端 小智'    ```

箭頭函數以詞法方式綁定this。 簡單來說,它使用來自其定義的外部函數this的值。

建議在需要使用外部函數上下文的所有情況下都使用箭頭函數。

4. 綁定上下文

現在讓咱們更進一步,使用ES6中的類重構Person

```JavaScript   class Person {       constructor(firstName, lastName) {         this.firstName = firstName;       this.lastName = lastName;     }  getFullName() {         return <code>${this.firstName} ${this.lastName}</code>;     }   }  const agent = new Person('前端', '小智');  agent.getFullName();        // => '前端 小智'    execute(agent.getFullName); // => 'undefined undefined'    ```

不幸的是,即使使用新的類語法,execute(agent.getFullName)仍然返回「undefined undefined」

在類的情況下,使用附加的變數self或箭頭函數來修復this的指向是行不通的。

但是有一個涉及bind()方法的技巧,它將方法的上下文綁定到構造函數中:

```JavaScript   class Person {       constructor(firstName, lastName) {         this.firstName = firstName;       this.lastName = lastName;  <pre><code>this.getFullName = this.getFullName.bind(this);   </code></pre>  }  getFullName() {         return <code>${this.firstName} ${this.lastName}</code>;     }   }  const agent = new Person('前端', '小智');  agent.getFullName();        // => '前端 小智'    execute(agent.getFullName); // => '前端 小智'    ```

構造函數中的this.getFullName = this.getFullName.bind(this)將方法getFullName()綁定到類實例。

execute(agent.getFullName) 按預期工作,返回'前端 小智'

5. 胖箭頭方法

bind 方式有點太過冗長,咱們可以使用胖箭頭的方式:

```JavaScript   class Person {       constructor(firstName, lastName) {         this.firstName = firstName;       this.lastName = lastName;     }  getFullName = () => {            return <code>${this.firstName} ${this.lastName}</code>;     }   }  const agent = new Person('前端', '小智');  agent.getFullName();        // => '前端 小智'    execute(agent.getFullName); // => '前端 小智'    ```

胖箭頭方法getFullName =() =>{…}綁定到類實例,即使將方法與其對象分離。

這種方法是在類中綁定this的最有效和最簡潔的方法。

6. 總結

與對象分離的方法會產生 this 指向不正確問題。靜態地綁定this,可以手動使用一個附加變數self來保存正確的上下文對象。然而,更好的替代方法是使用箭頭函數,其本質上是為了在詞法上綁定this

在類中,可以使用bind()方法手動綁定構造函數中的類方法。當然如果你不用使用 bind 這種冗長方式,也可以使用簡潔方便的胖箭頭表示方法。