VUE中的$set與$delete的原理


我們上文說了,Vue 是通過 Object.defineProperty 和重寫數組的原型方法來達到監控數據的目的。但是,在某些情況下,上面兩種方案無法做到監控數據的變化,例如:

(1):當我們給對象設置一個新屬性的時候,obj.newProperty = xxxxx;

(2):當我們刪除對象中的某個屬性的時候,delete obj.oldProperty;

上面兩種情況,Vue 的響應式系統都監控不到,為了彌補這兩個缺陷,Vue 提供了 $set 和 $delete API,當我們想設置新的屬性,或者刪除某個屬性的時候,不要用 js 原生的語法操作,而是使用 $set 和 $delete API 來完成任務。

這兩個 API 的思路其實和重寫數組的原型方法是一樣的,都是對 JS 中的某些原生操作進行重寫,當我們調用這些重寫的方法對數據進行操作的時候,Vue 自然就能監控到我們對數據做了哪些事情,進而做相對應的處理就可以了,接下來看源碼。

1,這兩個 API 是如何掛載到 Vue 原型中的

1-1,首先看 src/core/instance/index.js

function Vue (options) {
  // 如果當前的環境不是生產環境,並且當前命名空間中的 this 不是 Vue 的實例的話,
  // 發出警告,Vue 必須通過 new Vue({}) 使用,而不是把 Vue 當做函數使用
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // 執行 vm 原型上的 _init 方法,該方法在 initMixin 方法中定義
  this._init(options)
}
 
// 寫入 vm.$set、vm.$delete、vm.$watch
stateMixin(Vue)
 
export default Vue

掛載這兩個 API 到 Vue 原型上是在 stateMixin 方法中。

1-2,src/core/instance/state.js ==> stateMixin()

import {
  set,
  del
} from '../observer/index'
 
export function stateMixin (Vue: Class<Component>) {
  Vue.prototype.$set = set
  Vue.prototype.$delete = del
}

從上面的代碼可知,$set 和 $del API 的實現定義在 ../observer/index 文件中

 

2,$set 的實現

Vue.set 或者說是$set 原理如下

因為響應式數據 我們給對象和數組本身都增加了__ob__屬性,代表的是 Observer 實例。當給對象新增不存在的屬性 首先會把新的屬性進行響應式跟蹤 然后會觸發對象__ob__的 dep 收集到的 watcher 去更新,當修改數組索引時我們調用數組本身的 splice 方法去更新數組

/**
 * vm.$set 的底層實現
 */
export function set (target: Array<any> | Object, key: any, val: any): any {
  // 如果 target 是一個數組,並且 key 也是一個有效的數組索引值的話
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 設置數組的 length 屬性,設置的屬性值是 "數組原長度" 和 "key" 中的最大值
    target.length = Math.max(target.length, key)
    // 然后通過數組原型上的方法,將 val 添加到數組中
    // 在前面數組響應式源碼的閱讀中可以知道,通過數組原型的方法添加的元素,其是會被轉換成響應式的
    target.splice(key, 1, val)
    return val
  }
  // 這里用於處理 key 已經存在於 target 中的情況
  if (hasOwn(target, key)) {
    // 由於這個 key 已經存在於對象中了,也就是說這個 key 已經被偵測了變化,在這里,只不過是修改下屬性而已
    // 所以在這里,直接修改屬性,並返回 val 即可
    target[key] = val
    return val
  }
 
  const ob = (target: any).__ob__
 
  // 如果 target 沒有 __ob__ 屬性的話,說明 target 並不是一個響應式的對象
  // 所以在這里也不需要做什么額外的處理,將 val 設到 target 上,並且返回這個 val 即可
  if (!ob) {
    target[key] = val
    return val
  }
  // 如果上面所有的判斷條件都不滿足的話,說明用戶是在響應式數據上新增了一個數據,這種情況需要跟蹤這個新增屬性的變化
  // 在這里使用 defineReactive 將 val 變成 getter/setter 的形式
  defineReactive(ob.value, key, val)
  // 因為新增了一個屬性,所以 ob.value 變化了,所以在這里需要出發依賴的更新
  ob.dep.notify()
  return val
}

 

  

3,$delete 的實現

Vue.set 或者說是$set 原理如下

因為響應式數據 我們給對象和數組本身都增加了__ob__屬性,代表的是 Observer 實例。當給對象刪除一個已經存在的屬性 直接觸發對象__ob__的 dep 收集到的 watcher 去更新,當修改數組索引時我們調用數組本身的 splice 方法去更新數組

 

/**
 * Delete a property and trigger change if necessary.
 */
// Vue 對數據的監控是通過 Object.defineProperty() 實現的,所以當用戶通過 delete 關鍵字刪除某個字段時,Vue 是檢測不到的,
// 為了解決這個問題,Vue 提供了 vm.$delete 來解決這個問題
export function del (target: Array<any> | Object, key: any) {
  // 如果 target 是一個數組,並且 key 是一個下標值的話
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 執行數組原型上的 splice 方法,該方法會執行刪除的操作,並且會出發依賴的更新
    target.splice(key, 1)
    return
  }
  const ob = (target: any).__ob__
  // 如果 target 上面沒有 key 屬性的話,直接 return 即可,什么都不用干
  if (!hasOwn(target, key)) {
    return
  }
  // 使用 js 中原生的 delete 關鍵字刪除指定的 key
  delete target[key]
  // 在這里判斷 target 是不是響應式的,如果不是的話,就不用出發依賴的更新操作了。在這里,直接 return
  if (!ob) {
    return
  }
  // 出發依賴的更新操作
  ob.dep.notify()
}

 


免責聲明!

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



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