我發現了華點:vue規定用普通函式定義方法,為什麼react又要我用箭頭函式!

前端私教年年發表於2022-04-08

原文首發在我的公眾號,訂閱可第一時間檢視我的最新文章!

大家好,我是年年!

如果使用過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處理後繫結到元件例項。

如果覺得這篇文章對你有幫助,不要忘了給我點個贊,你的支援是我最大的動力???

關注我的公眾號可以第一時間看到最新文章???

點個在看更好

相關文章