官網解釋如下
由於 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方法來進行隱式觀察。