前言
得益於class properties proposal,React的程式碼變得簡潔了許多。在React中處理this繫結的時候,也習慣性地使用屬性宣告來替代過去constructor
賦值的方式
class A extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// do something
}
}
// arrow function
class B extends React.Component {
handleClick = () => {
// do something
}
}
複製程式碼
箭頭函式在宣告屬性的時候自動繫結 this
,省去了在constructor
中的函式覆蓋操作。
但是使用arrow function真的就一本萬利了嗎?
class中的arrow function
// code 1
class Base {
commonHandler = () => {
// do something
console.log('Base commonHandler', this.text);
}
text = 'base'
selfHandler() {
console.log('selfHandler')
}
}
class A extends Base {
text = 'A'
commonHandler(){
console.log('A commonHandler', this.text);
}
}
const a = new A();
a.commonHandler(); // Base commonHandler A
複製程式碼
上述程式碼最終的執行會有點讓人意外,為什麼text屬性發生了覆蓋,而commonHandler的函式卻還是執行父類?
我們通過babel編譯來先來看下這段程式碼在es2017下的結果
class Base {
constructor() {
// commonHandler被移到了constructor中
this.commonHandler = () => {
// do something
console.log('excute commonHandler', this.text);
};
this.text = 'base';
}
selfHandler() {
console.log('selfHandler');
}
}
複製程式碼
可以看到text
屬性和箭頭函式commonHandler
全被移到constuctor
中,而正常成員方法selfHandler
則沒有改變。es6的class中可以通過類似寄生組合式繼承進行模擬,編譯結果
class A
執行繼承的後在constructor
中將會呼叫父類的建構函式,而成員方法commonHandler
掛載到了A的prototype上,例項化後按照方法查詢的順序,先獲取的是例項上的方法。可以通過將code 1中的a.commonHandler()
替換為a.__proto__.commonHandler()
輸出了預期覆蓋父類方法的的結果'A commonHandler A'。
補充:class中的super在作為物件使用時,在普通方法中指向的是父的原型物件,所以如果父類的方法是通過arrow function定義
fn
,則該方法在子類中將無法通過類似super.fn()
進行呼叫
濫用arrow function的影響
那我們是不是可以在子類中也通過arrow function對commonHandler
進行繫結以達到覆蓋的目的?
就結果而言的確這樣做,可以根據上述對arrow function的分析,每指定一個arrow function都將會在contructor
中出現,也就意味著多個地方例項化,將會建立多個方法,而不是像通常定義在原型上面,這樣定義的方法在所有的例項上共享。
那是不是意味著我們要拋棄使用arrow function,答案是否定,我們這裡要杜絕的是濫用,僅僅做有必要繫結的。比如onClick={this.doSomething}
或fetch.then(this.hanldeDone)
附上一個優化方案:autobind-decorator核心思路是僅在必要時繫結作用域
參考: Arrow Functions in Class Properties Might Not Be As Great As We Think