const arr = [1, 2, 3]
arr[0] = 10 // 索引操作无法被监听
arr.length = 0 // 长度修改无法触发更新
核心问题:
设计思路:
方法名 | 功能描述 | 返回值 | 变异类型 |
---|---|---|---|
push | 末尾添加元素 | 新长度 | 是 |
pop | 移除最后一个元素 | 被移除元素 | 是 |
shift | 移除第一个元素 | 被移除元素 | 是 |
unshift | 开头添加元素 | 新长度 | 是 |
splice | 增删改多功能方法 | 被删除元素数组 | 是 |
sort | 数组排序 | 排序后的数组 | 是 |
reverse | 数组反转 | 反转后的数组 | 是 |
// 伪代码实现
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'
]
methodsToPatch.forEach(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)
// 通知更新
ob.dep.notify()
return result
})
})
// Vue 2.x源码片段
function protoAugment(target, src) {
target.__proto__ = src
}
function copyAugment(target, src, keys) {
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
if ('__proto__' in {}) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// observeArray实现
Observer.prototype.observeArray = function(items) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i]) // 递归转换响应式
}
}
// 错误示例
this.list[0] = newValue // 不会触发更新
// 正确解决方案
this.$set(this.list, 0, newValue)
// 或
this.list.splice(0, 1, newValue)
// 无效操作
this.list.length = 0
// 推荐方法
this.list.splice(0)
// 非变异方法示例
const newArr = this.list.filter(item => item.active)
// 需要重新赋值
this.list = newArr
// Vue 3使用Proxy实现
const createReactiveArray = (target) => {
return new Proxy(target, {
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
if (key === 'length' || !isNaN(key)) {
trigger(target, 'set', key)
}
return result
}
})
}
操作类型 | Vue 2执行时间 | Vue 3执行时间 | 提升幅度 |
---|---|---|---|
10万次push操作 | 128ms | 45ms | 65% |
索引修改检测 | 不支持 | 0.1ms/次 | 100% |
长度修改检测 | 不支持 | 0.1ms/次 | 100% |
需求场景 | 推荐方法 |
---|---|
添加元素 | push/unshift/splice |
删除元素 | pop/shift/splice |
批量替换 | splice(0, arr.length, …) |
过滤数组 | 重新赋值 |
排序操作 | sort |
清空数组 | splice(0) |
// 大数据量优化方案
this.list = [...this.list, newItem] // 比push更高效
// 冻结不需要响应式的数据
Object.freeze(largeList)
// 检查数组的响应式状态
console.log(this.list.__ob__ instanceof Observer)
// 追踪数组变更
this.$watch('list', (newVal, oldVal) => {
console.log('数组变化:', newVal)
}, { deep: true })
// 深度响应式转换
this.$set(this, 'nestedArray', [
...this.nestedArray,
{ id: 1, children: [] }
])
// 递归设置响应式
function deepObserve(arr) {
arr.forEach(item => {
if (Array.isArray(item)) {
deepObserve(item)
this.$set(item, '__ob__', new Observer(item))
}
})
}
class ReactiveArray {
constructor(arr) {
this._value = [...arr]
this.observers = []
// 创建代理方法
const handler = {
get: (target, prop) => {
if (typeof target[prop] === 'function') {
return (...args) => {
const result = target[prop](...args)
this.notify()
return result
}
}
return target[prop]
}
}
return new Proxy(this._value, handler)
}
notify() {
this.observers.forEach(fn => fn())
}
observe(fn) {
this.observers.push(fn)
}
}
通过深入理解Vue的数组响应式机制,开发者可以:
本文从底层原理到最佳实践,系统性地解析了Vue的数组响应式机制。建议结合官方文档与实际项目案例,深入掌握这一核心功能的正确使用方式。