代理数组
数组只是一个特殊的对象而已,因此想要更好地实现对数组的代理,就有必要了解相比普通对象,数组到底有何特殊之处。在之前的文章中我们知道在 JavaScript 中有两种对象:常规对象和异质对象。而这次要介绍的数组就是一个异质对象,这是因为数组对象的[[DefineOwnProperty]]
内部方法与常规对象不同。换句话说,数组对象除了[[DefineOwnProperty]]
这个内部方法之外,其他内部方法的逻辑都与常规对象相同。因此,当实现对数组的代理时,用于代理普通对象的大部分代码可以继续使用,如下所示:
1 | const arr = reactive(['foo']) |
上面这段代码能够按照预期工作。实际上,当我们通过索引读取或者设置数组元素的值时,代理对象的get/set
拦截函数也会执行,因此我们不需要做任何额外的工作,就能够让数组索引的读取和设置操作是响应式的了。
但对数组的操作与普通对象的操作仍然存在不同,下面总结了所有对数组元素或属性的“读取”操作。
- 通过索引访问数组元素值:arr[0]。
- 访问数组的长度:arr.length。
- 把数组作为对象,使用 for…in 循环遍历。
- 使用 for…of 迭代遍历数组。
- 数组的原型方法,如 concat/join/every/some/find/findIndex/includes 等,以及其他所有不改变原数组的原型方法。
可以看到,对数组的读取操作要比普通对象丰富得多。我们再来看看对数组元素或属性的设置操作有哪些。
- 通过索引修改数组元素值:arr[1] = 3。
- 修改数组长度:arr.length = 0.
- 数组的栈方法:push/pop/shift/unshift。
- 修改原数组的原型方法:splice/fill/sort 等。
除了通过数组索引修改数组元素值这种基本操作之外,数组本身还有很多会修改原数组的原型方法。调用这些方法也属于对数组的操作,有些方法的操作语义是“读取”,而有些方法的操作语义是“设置”。因此,当这些操作发生时,也应该正确的建立响应联系或触发响应。
从上面列出的这些对数组的操作来看,似乎代理数组的难度要比代理普通对象的难度大很多。但事实并非如此,这是因为数组本身也是对象,只不过它是异质对象罢了,它与常规对象的差异并不大。因此,大部分用来代理常规对象的代码对于数组也是生效的。