vue源碼解析:vue實例方法之set方法的實現原理


還記得,我們在將vue響應式原理的時候說過,Object.defineProperty()這個方法對對象的屬性方法的添加或者刪除不能做到實時的監聽,數組通過索引去 修改數組都是不能被檢測?所以vue實現了set方法,那么實現的set方法的原理是什么呢?

vm.$set( target, propertyName/index, value )

參數:

  • {Object | Array} target
  • {string | number} propertyName/index
  • {any} value

用法:

向響應式對象中添加一個屬性,並確保這個新屬性同樣是響應式的,且觸發視圖更新。它必須用於向響應式對象上添加新屬性,因為 Vue 無法探測普通的新增屬性。

  • 注意:對象不能是 Vue 實例,或者 Vue 實例的根數據對象。

源碼: src/core/observer/index.js

 1 export function set (target, key, val){
 2     if (process.env.NODE_ENV !== 'production' &&
 3         (isUndef(target) || isPrimitive(target))
 4        ) {
 5         warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
 6     }
 7     if (Array.isArray(target) && isValidArrayIndex(key)) {
 8         target.length = Math.max(target.length, key)
 9         target.splice(key, 1, val)
10         return val
11     }
12     if (key in target && !(key in Object.prototype)) {
13         target[key] = val
14         return val
15     }
16     const ob = (target: any).__ob__
17     if (target._isVue || (ob && ob.vmCount)) {
18         process.env.NODE_ENV !== 'production' && warn(
19             'Avoid adding reactive properties to a Vue instance or its root $data ' +
20             'at runtime - declare it upfront in the data option.'
21         )
22         return val
23     }
24     if (!ob) {
25         target[key] = val
26         return val
27     }
28     defineReactive(ob.value, key, val)
29     ob.dep.notify()
30     return val
31 }

 

首先,判斷在非生產環境,傳入的target如果是undefinednull或是原始類型,則直接跑出錯誤。

其次,如果判斷target如果是個數組,並且key是索引的話,那么就取當前數組長度與key這兩者的最大值作為數組的新長度,然后使用數組的splice方法將傳入的索引key對應的val值添加進數組。Array類型數據的變化偵測方式時說過,數組的splice方法已經被我們創建的攔截器重寫了,也就是說,當使用splice方法向數組內添加元素時,該元素會自動被變成響應式的。

接下來,如果傳入的target不是數組,那就當作是對象來處理。首先判斷傳入的key是否已經存在於target中,如果存在,表明這次操作不是新增屬性,而是對已有的屬性進行簡單的修改值,那么就只修改屬性值即可,接下來獲取到traget__ob__屬性,我們說過,該屬性是否為true標志着target是否為響應式對象,接着判斷如果tragte是 Vue 實例,或者是 Vue 實例的根數據對象,則拋出警告並退出程序,接着判斷如果ob屬性為false,那么表明target不是一個響應式對象,那么我們只需簡單給它添加上新的屬性,不用將新屬性轉化成響應式,最后,如果target是對象,並且是響應式,那么就調用defineReactive方法將新屬性值添加到target上,defineReactive方會將新屬性添加完之后並將其轉化成響應式,最后通知依賴更新。

總的來說set的執行流程應該是這樣的:

受 ES5 的限制,Vue.js 不能檢測到對象屬性的添加或刪除。因為 Vue.js 在初始化實例時將屬性轉為 getter/setter,所以屬性必須在 data 對象上才能讓 Vue.js 轉換它,才能讓它是響應的。要處理這種情況,我們可以使用$set()方法,既可以新增屬性,又可以觸發視圖更新。

舉例

給student對象新增age屬性

data () {
  return {
      student: {
          name: '',
          sex: ''
      }
  }
}
mounted () {
  this.student.age = 24
}

可以發現可以新增屬性,但是不會觸發更新視圖

當我們使用

mounted () {
  this.$set(this.student,"age", 24)
}

發現可以更新視圖

 

舉例:

<template>
   <div class="">
       <p>a: {{obj.a}}<p>
       <p>b: {{obj.b}}<p>
       <p>c: {{obj.c}}<p>
   </div>
<template>
<script>
export default {
   data () {
       return {
           obj: {
               a:0
          }
      }
  },
   created () {
       setTimeout(() => {
           this.obj.a = 1
      },200)
        setTimeout(() => {
          this.$set(this.obj, 'b', 2)
      },300)
        setTimeout(() => {
           this.obj.c = 3
      },400)
  }
}

結果:

1

2

分析原因:

vue不能檢測到對象屬性的添加或刪除。所以屬性必須在data對象上才能讓vue轉換它,讓它響應。所以當我需要觀測到這個對象的變化,目標對象沒有該屬性的時候,需要使用 $set。如果遇到對象賦值,視圖不更新,第一種在對象里面加上我們想要渲染的屬性,然后直接用this.obj.a 來修改,第二種用vue.set的方法,this.$set(this.obj, 'b', 2)來給對象加新屬性和賦值

 


免責聲明!

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



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