原文首發(fā)在我的公眾號(hào)寥粹,訂閱可第一時(shí)間查看我的最新文章!
大家好埃元,我是年年涝涤!
如果使用過(guò)react和vue,應(yīng)該發(fā)現(xiàn)過(guò)一個(gè)問題:vue告訴我們不應(yīng)該把方法岛杀、生命周期用箭頭函數(shù)去定義阔拳;而在react的類組件中,把方法寫成箭頭函數(shù)的形式卻更方便类嗤。
要問其原因糊肠,大部分人都只把他當(dāng)一個(gè)理所當(dāng)然的規(guī)定。但把這個(gè)問題剖開遗锣,其實(shí)能很好地把準(zhǔn)備面試時(shí)造的火箭货裹,在擰螺絲的時(shí)候用起來(lái)。
這篇文章可以讓你在這個(gè)實(shí)際場(chǎng)景中去用到this的指向精偿、作用域鏈以及原型弧圆。
this指向丟失
無(wú)論是vue還是react,都在官方文檔中強(qiáng)調(diào)笔咽,需要注意this的指向丟失搔预。但有趣的是,為了達(dá)到同樣的目的叶组,一個(gè)是不能使用箭頭函數(shù)拯田,一個(gè)是使用箭頭函數(shù)便能解決
React中this的丟失
首先來(lái)看看react,這是一個(gè)很普通的類組件寫法:
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'))
我在組件內(nèi)我定義了兩個(gè)方法:一個(gè)用箭頭函數(shù)實(shí)現(xiàn)扶叉,另一個(gè)用普通函數(shù)勿锅。在調(diào)用時(shí)分別打印this
,結(jié)果如下:
[圖片上傳失敗...(image-db8f01-1649345218334)]
箭頭函數(shù)中this正確指向了組件實(shí)例枣氧,但普通函數(shù)中卻指向了undefined溢十,為什么?
其實(shí)這是一個(gè)無(wú)關(guān)react的js特性达吞,剝離react帶來(lái)的心智負(fù)擔(dān)张弛,本質(zhì)上,上面的代碼不過(guò)是一個(gè)「類」,簡(jiǎn)化一下吞鸭,就變成了這樣??:
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()
運(yùn)行一下上面這段代碼寺董,會(huì)發(fā)現(xiàn)結(jié)果不出預(yù)料:在普通函數(shù)中this的指向也丟失了。
[圖片上傳失敗...(image-ca5172-1649345218334)]
從react代碼運(yùn)行的角度來(lái)解釋一下:
首先是事件觸發(fā)時(shí)刻剥,回調(diào)函數(shù)的執(zhí)行遮咖。回調(diào)函數(shù)不是像這樣直接由實(shí)例調(diào)用:reactIns.ordinaryFunMethod()
造虏,而是像上面代碼中的御吞,做了一次“代理”,最后被調(diào)用時(shí)漓藕,找不到調(diào)用對(duì)象了:ordinaryFunWithoutCaller()
陶珠。這時(shí)就出現(xiàn)了this指向undefined的情況。
但為什么使用箭頭函數(shù)享钞,this又可以正確指向組件實(shí)例呢揍诽?首先回顧一個(gè)簡(jiǎn)單的知識(shí)點(diǎn):class是個(gè)語(yǔ)法糖,本質(zhì)不過(guò)是個(gè)構(gòu)造函數(shù)栗竖,把上面的代碼用它最原始的樣子寫出來(lái):
'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()
可以看到:寫成普通函數(shù)的方法暑脆,是被掛載到原型鏈上的;而使用箭頭函數(shù)定義的方法划滋,直接賦給了實(shí)例饵筑,變成了實(shí)例的一個(gè)屬性,并且最重要的是:它是在「構(gòu)造函數(shù)的作用域」被定義的处坪。
我們知道根资,箭頭函數(shù)沒有自己的this,用到的時(shí)候只能根據(jù)作用域鏈去尋找最近的那個(gè)同窘。放在這里玄帕,也就是構(gòu)造函數(shù)這個(gè)作用域中的this
——組件實(shí)例。
這樣就可以解釋為什么react組件中想邦,箭頭函數(shù)的this能正確指向組件實(shí)例裤纹。
vue中this的丟失
把上面的組件用vue來(lái)寫一遍:
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')
運(yùn)行代碼,會(huì)發(fā)現(xiàn)結(jié)果對(duì)調(diào)了:使用箭頭函數(shù)反而導(dǎo)致了this指向丟失:this指向了window對(duì)象
[圖片上傳失敗...(image-ff05e3-1649345218334)]
這部分解釋起來(lái)會(huì)稍微復(fù)雜一下丧没,不過(guò)也只涉及一小塊vue源碼鹰椒。主要的操作是vue對(duì)組件方法的處理,最核心的就三行呕童,感興趣的可以去看看完整代碼:vue-github
function initMethods(vm: Component, methods: Object) {
for (const key in methods) {
vm[key] = bind(methods[key], vm)
}
}
vue會(huì)把我們傳入methods
遍歷漆际,再一個(gè)個(gè)賦給到組建實(shí)例上,在這個(gè)過(guò)程就處理了this的綁定(bind(methods[key], vm)
):把每一個(gè)方法中的this都綁定到組件實(shí)例上夺饲。
普通函數(shù)都有自己的this奸汇,所以綁定完后施符,被調(diào)用時(shí)都能正確指向組件實(shí)例。但箭頭函數(shù)沒有自己的this擂找,便無(wú)從談及修改戳吝,它只能去找父級(jí)作用域中的this。這個(gè)父級(jí)作用域是誰(shuí)呢贯涎?是組件實(shí)例嗎听哭?我們知道作用域只有兩種:全局作用域和函數(shù)作用域〖聿桑回到我們寫的vue代碼欢唾,它本質(zhì)就是一個(gè)對(duì)象(具體一點(diǎn),是一個(gè)組件的配置對(duì)象粉捻,這個(gè)對(duì)象里面有data、mounted斑芜、methods等屬性)也就是說(shuō)肩刃,我們?cè)谝粋€(gè)對(duì)象里面去定義方法,因?yàn)閷?duì)象不構(gòu)成作用域杏头,所以這些方法的父作用域都是全局作用域盈包。箭頭函數(shù)要去尋找this,就只能找到全局作用域中的this——window對(duì)象了醇王。
上面說(shuō)了這么多呢燥,總結(jié)一下:vue對(duì)傳入的方法methods
對(duì)象做了處理,在函數(shù)被調(diào)用前做了this指向的綁定寓娩,只有擁有this的普通函數(shù)才能被正確的綁定到組件實(shí)例上叛氨。而箭頭函數(shù)則會(huì)導(dǎo)致this的指向丟失。
結(jié)語(yǔ)
「為什么react中用箭頭函數(shù)棘伴,vue中用普通函數(shù)」這是一個(gè)挺很有意思的問題寞埠,簡(jiǎn)單來(lái)說(shuō),這種差異是由于我們寫的react是一個(gè)類焊夸,而vue是一個(gè)對(duì)象導(dǎo)致的仁连。
在類中定義只有箭頭函數(shù)才能根據(jù)作用域鏈找到組件實(shí)例;在對(duì)象中阱穗,只有擁有自身this的普通函數(shù)才能被修改this指向饭冬,被vue處理后綁定到組件實(shí)例。
如果覺得這篇文章對(duì)你有幫助揪阶,不要忘了給我點(diǎn)個(gè)贊昌抠,你的支持是我最大的動(dòng)力??????
關(guān)注我的公眾號(hào)可以第一時(shí)間看到最新文章??????
點(diǎn)個(gè)在看更好??????