淺探 Vue 為什么不增加數組下標響應式——為什么不能檢測到數組元素直接賦值


Vue 的雙向數據綁定,使得修改數據后,視圖就會跟着發生更新,比如對數組進行增加元素、切割等操作。然而直接通過下標修改數組內容后,視圖卻不發生變化。那么,在保留原有的數組響應方式下,為什么 Vue 不增加對數組下標的響應式監聽呢?

arr[index] = val 不是響應式的

在 Vue 官網的 列表渲染 — Vue.js 中,有強調 Vue 不能 直接檢測通過數組下標改變值的變化,需要通過 數組更新檢測 來實現。

<template>
  <div>
    <span v-for="i in arr">{{ i }}</span>
    <button @click="updateIndex">改變下標對應的值</button>
    <span v-for="key in Object.keys(obj)">{{ obj[key] }}</span>
    <button @click="updateKey">改變key對應的值</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      arr: [ 1, 2, 3, 4 ],
      obj: { a: 1, b: 2, c: 3, d: 4 }
    }
  },
  methods: {
    updateIndex() {
      this.arr[0]++                // 對數組這樣的操作不會引起視圖的更新
      // this.arr.splice(0, 0)     // 需要調用數組的方法,才能使視圖更新
    },
    updateKey() {
      this.obj['a']++    // 但對對象這樣會引起視圖更新
    }
  }
}
</script>

 

 

 

從源碼看 Vue 中數組的 Observer 實現

在 Vue 2.6.10 中,可以看到 Observer (/src/core/observer/index.js) 的實現方式:

export class Observer {
  // ......
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      // 這里對數組進行單獨處理
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      // 對對象遍歷所有鍵值
      this.walk(value)
    }
  }
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

可以看到 vue 對對象是采取 Object.keys然后 defineReactive 所有鍵值,而對數組並沒這樣做,而是只 observe 了每個元素的值,數組的下標因為沒有被監聽,所以直接通過下標修改值是不會更新視圖的。

而數組方法能夠響應式,是因為 Vue 對數組的方法進行了 def 操作 (/src/core/observer/array.js)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
methodsToPatch.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
  })
})

並非不能實現下標響應式

但數組也是對象的一種,它的下標就是它的鍵,只是平常使用時,數組的鍵數量往往比對象的鍵數量大的多。所以原則上它也是可以使用對象的處理方式。通過修改 源碼 后引入后查看效果:

export class Observer {
  // ....
  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
      this.walk(value)    // 保留原有的數組監聽方式下,增加對下標的監聽響應
    } else {
      this.walk(value)
    }
  }
  // ......
}

視圖代碼還是和上面的一樣,點擊按鈕可以看到視圖會實時更新:

 

實驗探測數組下標響應式對性能的影響

通過上面的修改,可以知道 Vue 其實是可以監聽數組下標的。但為什么 Vue 不采取,且說是“JavaScript的限制”呢?在 github issue#8562 中,Vue.js 作者尤雨溪解釋是因為性能問題。

為了驗證數組下標響應式對性能的影響,我做了以下實驗實現相同的效果,分別設置循環次數 TIMES 為 1000,10000,100000(以下只貼出關鍵代碼部分,其他部分代碼一致):

  1. 使用修改能響應下標觸發頁面更新的 Vue ,通過數組下標修改值 TIMES 次
<template>
  <div>
    <span v-for="i in arr">{{ i }}</span>
    <button @click="updateIndex">改變下標對應的值</button>
  </div>
</template>
<script>
// import modified vue
export default {
  data() {
    return {
      arr: new Array(100).fill(0)
    }
  },
  methods: {
    updateIndex() {
      console.time('updateIndex')
      for (let i = 0; i < TIMES; i++) {
        this.arr[0]++
      }
      console.timeEnd('updateIndex')
    }
  }
}
</script>
  1. 使用原版 vue 通過數組下標修改值 TIMES 次,並通過 splice 方法觸發視圖更新 
<template><!-- 和上面一樣 --></template>
<script>
// import origin vue
export default {
  data() { /* 和上面一樣 */ },
  methods: {
    updateIndex() {
      console.time('updateIndex')
      for (let i = 0; i < TIMES; i++) {
        this.arr[0]++
      }
      this.arr.splice(0, 0)    // 通過 splice 實現視圖更新
      console.timeEnd('updateIndex')
    }
  }
}
</script>

每個實驗不同 TIMES 都重復10次,取平均值,實驗數據如下:

 

增加數組下標響應式對性能會有影響

通過上面的實驗,可見在循環次數較少的時候,增加下標響應式似乎沒有多大影響,但隨循環次數增加,帶來的性能損耗將快速增加。如果想要實現直接修改下標對應的內容來自動更新視圖,對性能會有一些影響。因此對於數組的更新,最好還是通過數組更新檢測來實現。

在選擇 TIMES 取值的時候,也發現需要到 10000 級別才會體現出較明顯的差距。但一般情況下,我們並不會執行像上面一樣龐大的操作,也許僅僅只是改變一個值而已,實現下標響應式消耗的時間和普通的方式幾乎一樣,或許在這方面 vue 犧牲了一點開發體驗。

 轉自:https://blog.csdn.net/dobility/article/details/97261478


免責聲明!

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



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