Vue为何重写数组方法

132 阅读2分钟

重写的数组方法有8个: includes, indexOf, lastIndexOf,push, pop, shift, unshift, splice

includes, indexOf, lastIndexOf

这三个都是类似的,挑一个解释即可(includes)。

const raw = {}
function reactive(obj) {
    return new Proxy(obj, {
        get(target, key, receiver) {
           console.log(key)
           let res=Reflect.get(target, key, receiver)
            if (typeof res === 'object' && res !== null) {
               return reactive(res)
               }
            return res
          },
        set(target, property, value, receiver) {
          return Reflect.set(target, property, value, receiver);
        },
    });
}
const arr=reactive([raw])
console.log(arr.includes(arr[0])) //true?

includes内部执行流程是先获取length,再从下标0开始找到对应的值是否相同

上面的打印结果应该为true,但是运行代码发现是false,为什么呢?

首先arr[0]拿到的是一个代理对象,另外在includes会根据下标拿到对应的值,这个值也会变成一个代理对象,但是这两个代理对象不是相同的。因为即使给同一个原始对象进行两次代理,这两个代理也是不一样的

所以需要一个ProxyMap用来存储原始对象到代理对象的映射。每次调用 reactive 函数创建代理对象之前,优先检查是否已经存在相应的代理对象,如果存在,则直接返回已有的代理对象,这样就避免了为同一个原始对象多次创建代理对象的问题

上面并不是重写这三个方法的原因,只是优化而已,下面就是真正需要重写的原因,举个例子

console.log(arr.includes(raw)) 

沿用上面的代码,只不过现在打印这个,是true还是false。按照用户体验应该是true,但显然现在返回的是false. 因为在includes中获取元素是一个代理,现在用原始值去找肯定不匹配。

重写做的工作就是:先执行includes的默认行为,如果找不到,需要更改includes的this指向到原始对象

push, pop, shift, unshift, splice

这类函数重写的原因与effect相关,看下面这段代码

const arr = reactive([])
// 第一个副作用函数
effect(() => {
  arr.push(1)
})

// 第二个副作用函数
effect(() => {
  arr.push(1)
})

push会修改数组,并且会访问length。

执行第一个effect函数,这个副作用会与length建立联系。执行第二个effect函数,这个副作用也会与length建立联系,但是push会读取length的同时也会修改length,所以他会取出与length相关的所有副作用执行。也就是会执行第一个副作用函数。

此时,第二个副作用函数没有执行完,会执行第一个副作用函数。在第一个副作用函数中又会修改length,也就是会执行第二个副作用函数,这样循环往复,就会产生死循环。

重写做的工作就是:在执行默认行为之前,禁止追踪依赖(track),执行之后,允许track