原文首發在我的公眾號,訂閱可第一時間檢視我的最新文章!
大家好,我是年年!
如果使用過react和vue,應該發現過一個問題:vue告訴我們不應該把方法、生命週期用箭頭函式去定義;而在react的類元件中,把方法寫成箭頭函式的形式卻更方便。
要問其原因,大部分人都只把他當一個理所當然的規定。但把這個問題剖開,其實能很好地把準備面試時造的火箭,在擰螺絲的時候用起來。
這篇文章可以讓你在這個實際場景中去用到this的指向、作用域鏈以及原型。
this指向丟失
無論是vue還是react,都在官方文件中強調,需要注意this的指向丟失。但有趣的是,為了達到同樣的目的,一個是不能使用箭頭函式,一個是使用箭頭函式便能解決
?react
?vue
React中this的丟失
首先來看看react,這是一個很普通的類元件寫法:
class Demo extends React.Component{
state = {
someState:'state'
}
// ✅推薦
arrowFunMethod = () => {
console.log('THIS in arrow function:',this)
this.setState({someState:'arrow state'})
}
// ❌需要處理this繫結
ordinaryFunMethod(){
console.log('THIS oridinary function:',this)
this.setState({someState:'ordinary state'})
}
render(){
return (
<div>
<h2>{this.state.someState}</h2>
<button onClick={this.arrowFunMethod}>call arrow function</button>
<button onClick={this.ordinaryFunMethod}>call ordinary function</button>
</div>
)
}
}
ReactDOM.render(<Demo/>,document.getElementById('root'))
我在元件內我定義了兩個方法:一個用箭頭函式實現,另一個用普通函式。在呼叫時分別列印this
,結果如下:
箭頭函式中this正確指向了元件例項,但普通函式中卻指向了undefined,為什麼?
其實這是一個無關react的js特性,剝離react帶來的心智負擔,本質上,上面的程式碼不過是一個「類」,簡化一下,就變成了這樣?:
class ReactDemo {
// ✅推薦
arrowFunMethod = () => {
console.log('THIS in arrow function:', this)
}
// ❌this指向丟失
ordinaryFunMethod() {
console.log('THIS in oridinary function:', this)
}
}
const reactIns = new ReactDemo()
let arrowFunWithoutCaller = reactIns.arrowFunMethod
let ordinaryFunWithoutCaller = reactIns.ordinaryFunMethod
arrowFunWithoutCaller()
ordinaryFunWithoutCaller()
執行一下上面這段程式碼,會發現結果不出預料:在普通函式中this的指向也丟失了。
從react程式碼執行的角度來解釋一下:
首先是事件觸發時,回撥函式的執行。回撥函式不是像這樣直接由例項呼叫:reactIns.ordinaryFunMethod()
,而是像上面程式碼中的,做了一次“代理”,最後被呼叫時,找不到呼叫物件了:ordinaryFunWithoutCaller()
。這時就出現了this指向undefined的情況。
但為什麼使用箭頭函式,this又可以正確指向元件例項呢?首先回顧一個簡單的知識點:class是個語法糖,本質不過是個建構函式,把上面的程式碼用它最原始的樣子寫出來:
'use strict'
function ReactDemo() {
// ✅推薦
this.arrowFunMethod = () => {
console.log('THIS in arrow function:', this)
}
}
// ❌this指向丟失
ReactDemo.prototype.ordinaryFunMethod = function ordinaryFunMethod() {
console.log('THIS in oridinary function:', this)
}
const reactIns = new ReactDemo()
可以看到:寫成普通函式的方法,是被掛載到原型鏈上的;而使用箭頭函式定義的方法,直接賦給了例項,變成了例項的一個屬性,並且最重要的是:它是在「建構函式的作用域」被定義的。
我們知道,箭頭函式沒有自己的this,用到的時候只能根據作用域鏈去尋找最近的那個。放在這裡,也就是建構函式這個作用域中的this
——元件例項。
這樣就可以解釋為什麼react元件中,箭頭函式的this能正確指向元件例項。
vue中this的丟失
把上面的元件用vue來寫一遍:
const Demo = Vue.createApp({
data() {
return {
someState:'state',
}
},
methods:{
// ❌this指向丟失
arrowFunMethod:()=>{
console.log('THIS in arrow function:',this)
this.someState = 'arrow state'
},
// ✅推薦
ordinaryFunMethod(){
console.log('THIS in oridinary function:',this)
this.someState = 'ordinary state'
}
},
template:`
<div>
<h2>{{this.someState}}</h2>
<button @click='this.arrowFunMethod'>call arrow function</button>
<button @click='this.ordinaryFunMethod'>call ordinary function</button>
</div>`
})
Demo.mount('#root')
執行程式碼,會發現結果對調了:使用箭頭函式反而導致了this指向丟失:this指向了window物件
這部分解釋起來會稍微複雜一下,不過也只涉及一小塊vue原始碼。主要的操作是vue對元件方法的處理,最核心的就三行,感興趣的可以去看看完整程式碼:vue-github
function initMethods(vm: Component, methods: Object) {
for (const key in methods) {
vm[key] = bind(methods[key], vm)
}
}
vue會把我們傳入methods
遍歷,再一個個賦給到組建例項上,在這個過程就處理了this的繫結(bind(methods[key], vm)
):把每一個方法中的this都繫結到元件例項上。
普通函式都有自己的this,所以繫結完後,被呼叫時都能正確指向元件例項。但箭頭函式沒有自己的this,便無從談及修改,它只能去找父級作用域中的this。這個父級作用域是誰呢?是元件例項嗎?我們知道作用域只有兩種:全域性作用域和函式作用域。回到我們寫的vue程式碼,它本質就是一個物件(具體一點,是一個元件的配置物件,這個物件裡面有data、mounted、methods等屬性)也就是說,我們在一個物件裡面去定義方法,因為物件不構成作用域,所以這些方法的父作用域都是全域性作用域。箭頭函式要去尋找this,就只能找到全域性作用域中的this——window物件了。
上面說了這麼多,總結一下:vue對傳入的方法methods
物件做了處理,在函式被呼叫前做了this指向的繫結,只有擁有this的普通函式才能被正確的繫結到元件例項上。而箭頭函式則會導致this的指向丟失。
結語
「為什麼react中用箭頭函式,vue中用普通函式」這是一個挺很有意思的問題,簡單來說,這種差異是由於我們寫的react是一個類,而vue是一個物件導致的。
在類中定義只有箭頭函式才能根據作用域鏈找到元件例項;在物件中,只有擁有自身this的普通函式才能被修改this指向,被vue處理後繫結到元件例項。
如果覺得這篇文章對你有幫助,不要忘了給我點個贊,你的支援是我最大的動力???
關注我的公眾號可以第一時間看到最新文章???
點個在看更好