Skip to content

数组的拦截

数组的读取

js
const obj = {
  a: 1,
  b: 2,
  c: {
    aa: 11,
    bb: 22
  }
}
const arr = [1, obj, 3]
const proxyArr = reactive(arr)

✅ 没问题的情况:

js
proxyArr[0]
proxyArr.length
for (let key in proxyArr) {
  proxyArr[key]
}
proxyArr.includes(3) // true

❌ 错误的结果:

js
proxyArr.includes(obj) // false

情况不对劲,分析一下


js
//utils/enum.js
export const RAW = Symbol('raw') 

// behover/index.js

const arrayInstrumentations = {}  

;['includes', 'indexOf', 'lastIndexOf'].forEach((method) => {  
    arrayInstrumentations[method] = function (...args) {  
        // 1. 正常查找,从代理对象中查找
        const res = Array.prototype[method].apply(this, args)  
        // 如果找不到的话
        if (res < 0 || res === false) {  
            //给代理对象增加一个自定义标识,从原始对象找
            return Array.prototype[method].apply(this[RAW], args)  
        } 
        return res 
    } 
}) 


function get(target,key) {
    if (key === RAW) {  
        return target  
    }  
    trigger(target, TrackOpTypes.GET, key)
    // 如果是数组的某些方法,需要对数组的方法进行重写
    if (arrayInstrumentations.hasOwnProperty(key) && Array.isArray(target)) {  
        return arrayInstrumentations[key] 
    } 
    const result = Reflect.get(target,key)  
    if(isObject(result)) {  
        return  reactive(result)  
    }  
    return result
}
  • 之前的代码是将 result 作为一个代理对象 return,将代理对象与原始对象进行对比,所以返回错误结果
  • 为了避免属性名冲突 所以增加一个唯一标识 Symobl,同时改写数组的查询方法

数组的 写入

✅ 没问题的情况:

js
proxyArr[0] = 10
proxyArr.length = 1

❌ 错误的结果:

js
proxy[10] = 10 // 没有更改 length 属性

js
function set(target,key,value){
    // 是属性值的新增还是修改
    const type = target.hasOwnProperty(key) ? TriggerOpTypes.SET : TriggerOpTypes.ADD
    const oldValue = target[key]
    const result = Reflect.set(target,key,value)
    // 是否发生改变 决定是否派发更新
    if(hasChanged(oldValue,value)){ 
        trigger(target,TriggerOpTypes.SET,key) 
    }  
    // 如果 length 发生了改变,对 length 派发更新
    if(Array.isArray(target) && oldLength !== target.length) { 
        if(key !== 'length') { 
            trigger(target, TriggerOpTypes.SET, 'length') 
        } 
    } 
    return result 
}

但是这样的话,在删除的时候不能触发拦截

js
function set(target,key,value){
    // 是属性值的新增还是修改
    const type = target.hasOwnProperty(key) ? TriggerOpTypes.SET : TriggerOpTypes.ADD
    const oldValue = target[key]
    const oldLength = Array.isArray(target) ? target.length : undefined
    const result = Reflect.set(target,key,value)
    // 是否发生改变 决定是否派发更新
    if(hasChanged(oldValue,value)){ 
        trigger(target,TriggerOpTypes.SET,key) 
    }  
    // 如果 length 发生了改变,对 length 派发更新
    if(Array.isArray(target) && oldLength !== target.length) { 
        if(key !== 'length') {
            trigger(target, TriggerOpTypes.SET, 'length') 
        }else { 
            // 进行了删除操作         
            for (let i = target.length; i < oldLength; i++) { 
                trigger(target, TriggerOpTypes.DELETE, i.toString()) 
            } 
        } 
    }
    return result 
}

js
proxyArr.push(1)
proxyArr.push(2)

这样的话会一直触发依赖收集 这就需要增加自定义操作,选择是否触发依赖的收集

js
// effect/track.js 

/**
 * 依赖收集的触发暂停
 */
let shouldTrack = true
export function pauseTracking() { 
    shouldTrack = false
} 
export function enableTracking() { 
    shouldTrack = true
} 
export const track = (traget,type,key) => {  
    if(!shouldTrack) return
} 
js
// behover/index.js 
import { pauseTracking, enableTracking } from '../effect/track.js'
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach((method) => { 
    arrayInstrumentations[method] = function (...args) { 
        pauseTracking() 
        const res = Array.prototype[method].apply(this, args) 
        enableTracking() 
        return res 
    } 
}) 

最后更新时间: