《VueJs设计与实现》5.7.4 隐式修改数组长度的原型方法

114 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第16天,点击查看活动详情

上章我们学习了如何处理数组中的一些遍历方法,这些方法大部分都是不会更改数组本身的,因此我们只需要处理一些边际情况就可以了,但是数组中还有一些其他方法会修改原始数据,比如最常用的push方法。

查阅一下ECMA文档

image.png

这个文档不难理解,其实就是push这个方法具体步骤,或者叫伪代码。书中作者关注的是执行步骤,我却更关注这个条件,这里表示如果数组的length加上本次插入的长度如果大于Float范围那么就会抛出异常.

从这个文档中,我们能大概看出push的执行流程

  1. 判断数组的length加上参数的length有没有超过范围
  2. 遍历参数,设置数组内的值,然后length+1
  3. return length

所以,我们可以看出,在push的时候,即会读取数组的length,又会修改数组的length,如果我们只是执行一次,应该不会出什么问题,但如果我们执行了多次,那就可以会爆栈,为什么呢?

我们假设这样一句代码,我们push了2次。

const arr = reactive([])
effect(()=>{
    arr.push(1)
})
effect(()=>{
    arr.push(2)
})

当我们push第一次,我们储存并追踪了一个读length的副作用和修改length的副作用。 这时候我们push第二次,根据之前的代码,我们会执行全部副作用,包括第一次的,但这时候我们其实本身是在执行第二次push,却又返回执行了第一次push的副作用,而执行第一次又会执行全部,又会执行第二次。

这时候我们可怜的length属性就会不停被循环嵌套的副作用搞到崩溃,从而爆栈。

知道问题所在自然就知道解决的方法,我们需要在push的时候禁止追踪,push完再追踪即可

let shouldTrack = true
const arrayMethods = { 
    push(...args){
        shouldTrack = false
        let res = Array.prototype.push.call(this,args)
        shouldTrack = true
        return res
    }
}

const track = (target,key,receiver)=>{
    if(!activeEffect && shouldTrack){
     /*执行追踪逻辑*/
    }
}

同理,popshiftunshiftsplice这些方法都要这样处理,到这里,数组的响应就基本完成了。

下一章节会开始讲Set | Map的响应,这是ES6才有的对象,在vue2中,仅支持遍历,但是得益于Proxy,我们在vue3中也可以深度监听,明天继续一起看看如何处理吧