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
指向了 fish
,fish
正是定義了 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
永遠綁定了定義箭頭函式所在的那個物件