我们知道,Object.defineProperty不能检测数组的操作,因此需要拦截数组的操作(push, unshift, splice等)并重写他们,使之可以对新操作的数据进行检测。
拦截数组方法的思路:
- 缓存数组的操作方法,直接缓存Array.prototype就行
- 使用一个代理数组,重写代理数组的操作方法,代理数组的原型指向Array.prototype
- 实例继承代理数组
代码实现
const arrayProto = Array.prototype // step1
const arrayMethods = Object.create(arrayProto) // step2
const methodsToPath = ['push','pop','shift']
methodsToPath.forEach(function(method){
const original = arrayProto[method] // 缓存方法
def(arrayMethods,method,function mutator(...args){
// 执行缓存的方法以保证重新的方法与数组原方法功能一致
const result = original.apply(this,args)
//无论是数组还是对象都会被定义一个__ob__属性,
// 并且__ob__,dep手机了所有该对象(或数组)的依赖(观察者)
const ob = this._ob__
//省略了部分代码
......
//notify change
ob.dep.notify()
return result
})
})
上述代码,通过重写arrayMethods上的数组操作方法,实现了在操作数组时,通知依赖更新。
你以为这就完成了吗? 不。还需要补充一点。
操作数组之后,数组发生了什么变化?
答: 数组增加了元素
,删除了元素
, 变更了元素顺序
(还有可能是元素被替换了,不过替换可以理解为先删除后增加两个动作)。
重点来看增加了元素
, 即push
, unshift
, splice
这三个方法,为什么要重点关注呢? 因为新增加的元素是非响应式的,所以需要获取这些元素,并将其变为响应式数据才行。
// 上面所省略的那部分代码
...
const ob = value.__proto__
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()
...
splice()方法向/从数组中 添加/删除项目,然后返回被删除的项目,该方法会改变原始数组
arrayObj.splice(index, homany, item1, ..., itemX)
1. index: 必需,添加或删除项目的位置索引
2. howmany: 必需,要删除的项目数量,如设置为0,表示不删除
3. item1,...itemX: 可选,向数组中添加的新项目
// 上述 inserted = args.slice(2) 就是取 第三项之后的所有项