Vue.set()和this.$set()源碼解析


前言

我們在日常項目開發過程中,有時候我們對數組或者對象進行了一些操作后,發現頁面數據沒有更新到。這個時候就會有疑問,why?

如果我們在看文檔有這樣一個api,以下內容:

 

 

Vue.set()和this.$set()實現原理

Vue.set()的源碼:  ... 這里是省略的代碼

import { set } from '../observer/index'

...
Vue.set = set
...

this.$set()的源碼:

import { set } from '../observer/index'

...
Vue.prototype.$set = set
...

從上面兩個源碼中,我們發現Vue.set()和this.$set()這兩個api的實現原理基本一模一樣,都是使用了set函數。set函數是從 ../observer/index 文件中導出的,區別在於Vue.set()是將set函數綁定在Vue構造函數上,this.$set()是將set函數綁定在Vue原型上。

接下來看下 ../observer/index 的set函數:

function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
// 數組
if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) target.splice(key, 1, val) return val }
// 對象
if (key in target && !(key in Object.prototype)) { target[key] = val return val } const ob = (target: any).__ob__ if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } if (!ob) { target[key] = val return val } defineReactive(ob.value, key, val) ob.dep.notify() return val }

set函數接收三個參數分別為 target、key、val,其中target的值為數組或者對象。

 

具體代碼分析:

Step1

if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }

isUndef和isPrimitive方法,從名字就可以看出,isUndef是判斷target是不是等於undefined或者null。isPrimitive是判斷target的數據類型是不是string、number、symbol、boolean中的一種。所以這里的意思是如果當前環境不是生產環境並且 isUndef(target) || isPrimitive(target) 為真的時候,那么就拋出錯誤警告。

 

分析數組這塊代碼前,我們先來看下vue數組與普通js數組的區別:

 

 

 

 

 

 

 

vue中的數組我們命名為arrVue,js中的普通數組命名為arrJs。我們平時調用普通數組的push、pop等方法是調用的Array原型上面定義的方法, 從圖中可以看出arrJs的原型是指向Array.prototype,也就是說 arrJs.__proto__ == Array.prototype

但是在vue的數組中,arrVue的原型其實不是指向的Array.prototype,而是指向的一個對象(我們這里給這個對象命名為arrayMethods)。arrayMethods上面只有7個push、pop等方法,並且arrayMethods的原型才是指向的Array.prototype。所以在vue中調用數組的push、pop等方法時其實不是直接調用的數組原型給我們提供的push、pop等方法,而是調用的arrayMethods給提供的push、pop等方法。vue為什么要給數組的原型鏈上面加上這個arrayMethods呢?這里涉及到了vue的數據響應的原理。暫時理解成vue在arrayMethods對象中做過了特殊處理,如果調用了arrayMethods提供的push、pop等7個方法,那么它會觸發當前收集的依賴(這里收集的依賴可以暫時理解成渲染函數),導致頁面重新渲染。換句話說,對於數組的操作,只有使用arrayMethods提供的那7個方法才會導致頁面渲染,這也就解釋了為什么我們使用 vueInstance.$data.arr[0] = 3;時不會導致頁面出現渲染。

 

Step2


if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) target.splice(key, 1, val) return val }

if判斷當前target是不是數組,並且key的值是有效的數組索引。

然后將target數組的長度設置為target.length和key中的最大值,為了防止我們傳入key下標超過數組長度導致報錯。

調用arrayMethods提供的push、pop等7個方法可以導致頁面重新渲染,這里使用splice是arrayMethods提供的7個方法中的一種。 

這塊代碼意思是在修改數組時調用set方法時讓我們能夠觸發響應的代碼

 

Step3

if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }

如果key本來就是對象中的一個屬性,並且key不是Object原型上的屬性。說明這個key本來就在對象上面已經定義過了的,直接修改值就可以了,可以自動觸發響應。

const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  if (!ob) {
    target[key] = val
    return val
  }

定義變量ob的值為 ```target.__ob__```,vue給響應式對象都加了一個__ob__屬性,如果一個對象有這個__ob__屬性,那么就說明這個對象是響應式對象,我們修改對象已有屬性的時候就會觸發頁面渲染。 

```target._isVue || (ob && ob.vmCount) ```的意思是:當前的target對象是vue實例對象或者是根數據對象,那么就會拋出錯誤警告。

```if (!ob)```為真說明當前的target對象不是響應式對象,不需要響應,那么直接賦值返回即可。比如:

```let obj = {o: 3}; this.$set(obj1, 'o', 2);```

  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val

這里才是vue.set()真正處理對象的地方。```defineReactive(ob.value, key, val)```的意思是給新加的屬性添加依賴,以后再直接修改這個新的屬性的時候就會觸發頁面渲染。 ```ob.dep.notify()```這句代碼的意思是觸發當前的依賴(這里的依賴依然可以理解成渲染函數),所以頁面就會進行重新渲染。

 


  本文分享到這里,給朋友們推薦一個前端公眾號 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM