為什麼Vue不能觀察到陣列length的變化?

buyue發表於2019-03-01

官網解釋如下

由於 JavaScript 的限制,Vue 不能檢測以下變動的陣列:
當你利用索引直接設定一個項時,例如:vm.items[indexOfItem] = newValue
當你修改陣列的長度時,例如:vm.items.length = newLength

因為vue的響應式是通過Object.defineProperty來實現的,但是陣列的length屬性是不能新增getter和setter,所有無法通過觀察length來判斷。

例子

如下程式碼,雖然看起來陣列的length是10,但是for in的時候只能遍歷出0, 1, 2,導致了只有前三個索引被加上了getter 和setter

var a = [0, 1, 2]
a.length = 10
// 只是顯示的給length賦值,索引3-9的對應的value也會賦值undefined
// 但是索引3-9的key都是沒有值的
// 我們可以用for-in列印,只會列印0,1,2
for (var key in a) {
  console.log(key) // 0,1,2
}
複製程式碼

那麼vue提供了一些解決方法

使用內建的Vue.$set

讓陣列顯式的進行某個索引的觀察
Vue.set(array, indexOfItem, newValue)

實際上是呼叫了

Object.defineProperty(array, indexOfItem, {
  enumerable: true,
  configurable: true,
  get() { },
  set(newVal) { }
})
複製程式碼

這樣可以手動指定需要觀察的key,那麼就可以達到預期的效果。

重寫了 push, pop, shift, unshift, splice, sort, reverse方法

Vue原始碼

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

/**
 * Intercept mutating methods and emit events
 */
;[
  `push`,
  `pop`,
  `shift`,
  `unshift`,
  `splice`,
  `sort`,
  `reverse`
]
.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case `push`:
      case `unshift`:
        inserted = args
        break
      case `splice`:
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})
複製程式碼

這些是在Array.__proto__上 進行了方法重寫或者新增

並且對新增屬性的方法如push,unshift,splice所新增進來的新屬性進行手動觀察,原始碼為

  if (inserted) ob.observeArray(inserted)
複製程式碼

對以上方法進行了手動的進行訊息觸發

  ob.dep.notify()
複製程式碼

結論

vue對陣列的length直接改變無法直接進行觀察,提供了vue.$set 進行顯式觀察,並且重寫了 push, pop, shift, unshift, splice, sort, reverse方法來進行隱式觀察。

相關文章