在响应系统中,当我们代理数组时,是否需要做一些改变?在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()
}
})
}