1. 程式人生 > >React中的事件處理為什麼要bind this?

React中的事件處理為什麼要bind this?

這個回答非常清晰,轉載自知乎-dmumatt:

 

 

程式碼一:

// 使用 ES6 的 class 語法
class Cat {
 sayThis () {
 console.log(this); // 這裡的 `this` 指向誰?
  }

 exec (cb) {
 cb();
  }

 render () {
 this.exec(this.sayThis);
  }
}

const tom = new Cat();
tom.render(); // 輸出結果是什麼?

 

程式碼二:

const jerry = {
 sayThis: function () {
 console.log(this); // 這裡的 `this` 指向誰?
  },

 exec: function (cb) {
 cb();
  },

 render: function () {
 this.exec(this.sayThis);
  },
}

jerry.render(); // 輸出結果是什麼?


 

代第一段程式碼的結果是 undefined ,這和第一節中 React 出現的結果完全一致。這代表 this

指向了 undefined 。其實是 JS 的行為而並非 React。

第二段程式碼的結果是,你所使用的環境裡面的全域性物件——在瀏覽器中就是 window 物件,在 Node.js 中就是 global 物件。

你看到輸出結果的時候,一定感到很困惑吧?到底 this 幹了什麼??


JS 中的 this

this 不指向定義它的函式的那個物件的情形

var name = 'Global'
const fish = {
 name: 'Fish',
 greet: function() {
 console.log('Hello, I am ', this.name);
  }
};

fish.greet(); // Hello, I am  Fish

const greetCopy = fish.greet;

greetCopy(); // Chrome: Hello, I am  Global
// Node.js: Hello, I am undefined

當你使用“點”操作符 . 來呼叫 greet 函式的時候,fish.greet()this 指向了 fishfish 正是定義了 greet 方法的那個物件。在這種情況下,我們稱 fish 是這個函式的呼叫者

事實上,fish.greet 在記憶體中只是一個普通的函式。不管它是在什麼物件中定義的,它都可以和普通的函式一樣,賦值給另一個變數,比如前面的 greetCopy 。如果你用 console.log 列印 console.log(fish.greet) 或者 console.log(greetCopy) ,控制檯輸出的結果都是一樣的。

console.log(fish.greet); // function () { … }
console.log(greetCopy); // function () { … }

如果你不用呼叫者顯式地呼叫一個函式,JS 的直譯器就會把全域性物件當作呼叫者。所以 greetCopy() 這個語句在 Chrome 中的行為就和 greetCopy.call(window) 是一樣的,在 Node.js 中就和 greetCopy.call(global) 是一樣的。

但是有一種例外,如果你使用了嚴格模式,那麼沒有顯式的使用呼叫者 的情況下, this 永遠不會自動繫結到全域性物件上。如果此時你呼叫 greetCopy ,你就會得到報錯,因為這時候 this 不指向任何物件,this 這時候就是 undefined

'use strict';

var name = 'Global'
const fish = {
 name: 'Fish',
 greet: function() {
 console.log('Hello, I am ', this.name);
  }
};

fish.greet(); // Hello, I am  Global

const greetCopy = fish.greet;

greetCopy(); // Uncaught TypeError: Cannot read property 'name' of undefined

注意,在上面這種情況下,greetCopy 在 Chrome 中和在 Node.js 中行為不太一樣。正如你看到的那樣,在 Node.js 中,this.name 的值是 undefined 。在瀏覽器中,如果你在最外層作用於定義了一個變數,它就會自動變成全域性物件的一個屬性。相反,在 Node.js 中,最外層物件不會自動被賦給全域性物件,除非你顯式地使用 global.name = 'Global'

如果我想使用另一個物件作為呼叫者來呼叫 fish.greet ,我該怎麼做?這時候就要用到 Function.prototype.call

// 前面程式碼一的上下文

const pig = {
 name: "Pig"
};

fish.greet.call(pig); // Hello, I am  Pig

call 方法強制性地把 fish.greet呼叫者繫結到了 pig 物件上,pig 這時候用作 this 方法的引數。

 

回撥函式中的 this

回撥函式簡單的來說,就是把一個函式作為另一函式的引數,並且在另一個函式執行的時候呼叫這個函式。看一下下面的例子:

var name = 'Global';

const matt = {
 name: "Matt",
 sayName: function () {
 console.log(this.name);
    }
}

function exec(cb) {
 cb();
}

exec(matt.sayName); // `Global` (瀏覽器), `undefined` (Node.js)

如果你閱讀了上面的章節,這個輸出結果對你來說就很好理解了。我們來看一下在直譯器呼叫 exec() 函式的時候都做了什麼。

當這個程式執行到 exec 函式的時候,實參 matt.sayName 被傳遞給了形參 cb 。這就和前面的章節中說的賦值語句的情況類似:const greetCopy = fish.greet; 。這裡 cb 在呼叫的時候並沒有顯式的呼叫者,所以此時,this非嚴格模式下就會指向全域性物件,在嚴格模式下就會指向 undefined

我們來看一下另一個很相似的情形。思考一下結果是什麼?

const jerry = {
 sayThis: function () {
 console.log(this); // `this` 指向什麼?
  },

 exec: function (cb) {
 cb();
  },

 render: function () {
 this.exec(this.sayThis);
  },
}

jerry.render(); // 輸出結果是什麼?

是的!你在上一章中看到了這個例子了。你現在一定知道了為什麼輸出結果是全域性物件了吧!

即使我們使用點操作符. 來顯式地呼叫 exec 方法,然而 cb 函式仍然沒有一個顯式的呼叫者。因此,你就會看到 this 指向了全域性物件。

下面這句話非常重要!一般人可能不知道

如果你使用了 ES6 的 class 語法,所有在 class 中宣告的方法都會自動地使用嚴格模式

當你使用 onClick={this.handleClick}來繫結事件監聽函式的時候,handleClick 函式實際上會作為回撥函式,傳入 addEventListener() 。這就是為什麼你在 React 的元件中新增事件處理函式為什麼會得到 undefnied 而不是全域性物件或者別的什麼東西。

箭頭函式

箭頭函式使得 this 更簡單和直接。

關於箭頭函式的資料其實很多,在這裡我就不多說了。你只要記住一個規則就足夠了,如果你仔細閱讀了上文,你應該能理解這個規則

this 永遠綁定了定義箭頭函式所在的那個物件