vue.js设计与实现(阅读)-五(代理数组)

76 阅读2分钟

在响应系统中,当我们代理数组时,是否需要做一些改变?在JavaScript中有两种对象,第一种是常规对象,第二种是异质对象;数组便是属于第二种。

这是因为数组对象的 [[DefineOwnProperty]] 内部方法与常规对象不同。换句话说,数组对象除了 [[DefineOwnProperty]] 这个内部方法之外,其他内部方法的逻辑都与常规对象相同。因此,当实现对数组的代理时,用于代理普通对象的大部分代码可以继续使用;

比如:

 const arr = createReactive(['foo'])

    effect(()=>{
       console.log(arr[0])
    })
    
    arr[0]='bar' // 能够触发响应

上面这段代码能够按预期工作。实际上,当我们通过索引读取或设置数组元素的值时,代理对象的 get/set 拦截函数也会执行,因此我们不需要做任何额外的工作,就能够让数组索引的读取和设置操作是响应式的了。 但对数组的操作与对普通对象的操作仍然存在不同,下面总结了所有对数组元素或属性的“读取”操作。

  • 通过索引访问数组元素值:arr[0]。
  • 访问数组的长度:arr.length。
  • 把数组作为对象,使用 for...in 循环遍历。
  • 使用 for...of 迭代遍历数组。
  • 数组的原型方法concat/join/every/some/find/findIndex/includes 等,以及其他所有不改变原数组的原型方法
数组的索引与 length 修改实现

set方法修改

 set(target,key,newVal,receiver){
                if(isReadonly){
                    console.log(`属性${key}是只读的`)
                    return true
                }
                const oldValue = 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(target===receiver.raw){
                    if(oldValue!==newVal&&(oldValue===oldValue||newVal===newVal)){
                        // 传入第四个参数值,用来判断数组的length属性
                        trigger(target,key,type,newVal)
                    }
                }
                return res
            },

trigger方法修改

function trigger(target,key,type,newVal){
        let depsMap = bucket.get(target)
        if(!depsMap) return
        let effects = depsMap.get(key)
        const effectsToRun = new Set()
        effects && effects.forEach(effectFn=>{
            if(activeEffect!==effectFn){
                effectsToRun.add(effectFn)
            }
        })
        // 如果是数组,应该取出并执行与length属性相关的副作用函数
        if(type === 'ADD' && Array.isArray(target)){
            const lengthEffects = depsMap.get('length')
            lengthEffects && lengthEffects.forEach(effectFn=>{
                if(effectFn!==activeEffect){
                    effectsToRun.add(effectFn)
                }
            })
        }
        // 如果操作目标是数组,并且修改了数组的length属性,索引大于或者等于length值的元素  取出
        if(Array.isArray(target) && key === 'length'){
            depsMap.forEach((effects,key)=>{
                if(key>=newVal){
                    effects.forEach(effectFn=>{
                        if(effectFn!==activeEffect){
                            effectsToRun.add(effectFn)
                        }
                    })
                }
            })
        }
        if(type==='ADD' || type ==='DELETE'){
            const iterateEffects = depsMap.get(ITERATE_KEY)
            // 将与ITERATE_KEY相关联的副作用函数也添加到effectsToRun
            iterateEffects && iterateEffects.forEach(effectFn=>{
                if(activeEffect!==effectFn){
                    effectsToRun.add(effectFn)
                }
            })
        }

        effectsToRun.forEach(effectFn=>{
            if(effectFn.options.scheduler){
                effectFn.options.scheduler()
            }else{
                effectFn()
            }
        })
    }