Vue响应式数据之Array
修改原型方法
上面所说到的是对象的响应式,但 js 中不止有对象,还有数组,数组能用 Object.defineProperty 方式来监听吗,能
const original = Array.prototype.push
Array.prototype.push = function (...args) {
console.log('ADD', args)
return original.apply(this, args)
}
const arr = [1, 2, 3]
arr.push(4)
// 输出 ADD 4
当然,这里修改了全局的 Array 原型,对于一些不必要的数据也会监听到,在 Vue2 中会进入 Observer 构造函数体,判断 value 是否为数组,是则对 value 原型赋值为修改后的 arrayMethods。
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
if (Array.isArray(value)) {
value.__proto__ = arrayMethods
}
至于以及其他数组方法,这里仅做代码实现,由于篇幅有限,不做细说。
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
function def(obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true,
})
}
;['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function (method) {
const original = arrayProto[method]
def(arrayMethods, method, function mutator(...args) {
const result = original.apply(this, args)
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) {
console.log('ADD', args)
}
return result
})
})
function observerArray(arr) {
arr.__proto__ = arrayMethods
return arr
}
let arr = observerArray([1, 2, 3])
arr.push(4)
arr.unshift(0)
console.log(arr)
输出如下
ADD [ 4 ]
ADD [ 0 ]
[ 0, 1, 2, 3, 4 ]
缺陷
通过一系列原型方法修改来实现响应式也有缺陷,尤其对于 数组特殊变动并没有对应原型方法。
- 利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 修改数组的长度时,例如:
vm.items.length = newLength
Proxy
但在 Vue3 也可以使用 Proxy 来监听(代理)数据,先引用监听Object 中的最终代码,对其稍加修改一下,看看效果
function log(type, index, val) {
console.log(type, index, val)
}
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
if (typeof res === 'object' && res !== null) {
return reactive(res)
}
if (Array.isArray(target) && isNaN(key)) {
return res
}
log('GET', key, res)
return res
},
set(target, key, newVal, receiver) {
const oldVal = target[key]
const type = Array.isArray(target) ? (Number(key) < target.length ? 'SET' : 'ADD') : Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
const res = Reflect.set(target, key, newVal, receiver)
if (Array.isArray(target) && key === 'length') {
// log('Length', null, target.length)
} else {
if (oldVal !== newVal) {
log(type, key, newVal)
}
}
return res
},
deleteProperty(target, key) {
const hadKey = Object.prototype.hasOwnProperty.call(target, key)
const res = Reflect.deleteProperty(target, key)
if (res && hadKey) {
log('DELETE', key, res)
}
return res
},
})
}
const target = [1, 2, 3]
const p = reactive(target)
p[1]
p.push(4)
p[2] = 100
p.pop()
console.log(p)
执行结果
GET 1 2
ADD 3 4
SET 2 100
GET 3 4
DELETE 3 true
[ 1, 2, 100 ]
实际上,以上代码就已经能监听数组成员新增,修改与删除了。但对于一些特殊方法(数组遍历,寻找成员),还需要修改其原型方法,就需要像 Vue2 对原型方法那样操作。不过在监听数据变化上,用处并不是特别大,主要体现在依赖收集以及副作用函数的调用上。