最近在React官網學習Handling Events這一章時,有一處不是很明白。代碼如下:
class Toggle extends React.Component { constructor(props) { super(props); this.state = {isToggleOn: true}; // This binding is necessary to make `this` work in the callback this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState(prevState => ({ isToggleOn: !prevState.isToggleOn })); } render() { return ( <button onClick={this.handleClick}> {this.state.isToggleOn ? 'ON' : 'OFF'} </button> ); } } ReactDOM.render( <Toggle />, document.getElementById('root') );
注意到在Toggle類的構造函數constructor類中,有一句注釋:“This binding is necessary to make `this` work in the callback”,即在構造函數中,利用Function.bind()函數將類中已有的handleClick函數再次綁定了一下this。對於這個做法,官網給出的注釋是:
這段話說了看似說了很多,其實就兩點:
1.如果你不綁定this.handleClick方法,那么在事件發生並且精確調用這個方法時,方法內部的this會丟失指向。
2.這不是React的原因,這是JavaScript中本來就有的。如果你傳遞一個函數名給一個變量,然后通過在變量后加括號()來調用這個方法,
此時方法內部的this的指向就會丟失
這一段點明了為什么要在構造函數中綁定this,因為JavaScript中確實有這么一個陷阱。具體是怎么樣的呢?我進行了一下測試:
let obj = { tmp:'Yes!', testLog:function(){ console.log(this.tmp); } }; obj.testLog();
為了便於為學習ES6的童鞋理解以及說明這是JavaScript中的陷阱而非React所特有,這里使用字面量表達式聲明對象。
經過測試,這樣使用obj中的testLog方法時,this指向obj,能夠正常輸出tmp屬性:
現在修改一下代碼:
let obj = { tmp:'Yes!', testLog:function(){ console.log(this.tmp); } }; let tmpLog = obj.testLog; tmpLog();
注意到現在沒有直接調用obj對象中的testLog方法,而是使用了一個中間變量tmpLog過渡,當使用括號()調用該方法時,方法中的this丟失了指向,會指向window,進而window.tmp未定義就是undefined:
說了這么多,跟React事件處理函數的綁定有什么關系呢?
前面講過,React跟原生JavaScript的事件綁定區別有兩點,其中第二點就是:
即在React(或者說JSX)中,傳遞的事件參數不是一個字符串,而是一個實實在在的函數:
這樣說,React中的事件名(上圖中的onClick)就是我所舉例子中的中間變量,React在事件發生時調用onClick,由於onClick只是中間變量,所以處理函數中的this指向會丟失,為了解決這個問題,我們需要在實例化對象的時候,需要在構造函數中綁定this,使得無論事件處理函數如何傳遞,它的this的指向都是固定的,固定指向我們所實例化的對象。
這個JavaScript陷阱是對我之前文章關於JavaScript中的this的一個很好的補充。
另外,在后續學習當中,發現可能存在其他原因,具體參見阮一峰的博客:ES6 class語法中this的指向