对数组元素或者属性的读取操作:
通过索引访问数组元素值:arr[0]
访问数组的长度:arr.length
把数组作为对象,使用for…in循环遍历
使用for…of迭代遍历数组
数组的原型方法,如concat/join/every/some/find/findIndex/includes等,以及不改变原数组的原型方法
1 数组索引与length
通过索引设置数组元素的值时,会执行内部方法[[Set]],内部方法[[Set]]依赖于[[DefineOwnProperty]],当设置的索引值大于数组当前长度,更新数组length属性,触发与length属性相关联的副作用函数重新执行,修改set拦截函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | set(target,key,newValue){                          if(isReadOnly){                 console.warn(`属性${key}是只读的`)             }                          const oldValue=target[key]                                       const type=Array.isArray(target)                         ?Number(key)<length?'SET':'ADD'                         :Object.prototype.hasOwnProperty.call(target,key)?'SET':'ADD'                                      const res=Reflect.set(target,key,receiver,newValue)                          if(target===receiver.raw){                                  if(oldValue!==newValue&&(oldValue===oldValue||newValue===newValue)){                                         trigger(target,key,type,newValue)                 }             }                          return true;         }
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | function trigger(target,key){     const depsMap=bucket.get(target)     if(!depsMap)return     const effects=depsMap.get(key)     const effectsToRun=new Set(effects)          if(type==='ADD'||type==='DELETE'){         const iterateEffects=depsMap.get('length')         lengthEffects&&lengthEffects.forEach(effectfn=>{             if(effectfn!==activeEffect){                 effectsToRun.add(effectfn)             }         })     }     effects&&effects.forEach(effectfn=>{         if(activeEffect!=effectfn){             effectsToRun.add(effectfn)         }     })        effectsToRun.forEach(effectfn=>effectfn())      }
  | 
 
2 数组查找方法
arr.includes(arr[0])中arr是代理对象,includes函数执行时this指向的是代理对象,即arr,includes方法会通过索引读取数组元素值,如果值时可以被代理的,那么得到的值就是新的代理对象,
1 2 3 4 5
   | function reactive(obj){          return createReactive(obj)      }
  | 
 
解决方法:
1 2 3 4 5 6 7 8 9 10 11 12 13
   | 
  const reactiveMap=new Map() function reactive(obj){          const existionProxy=reactiveMap.get(obj)     if(existionProxy)return existionProxy          proxy=createReactive(obj)     reactiveMap.set(obj,proxy)     return proxy      }
 
  | 
 
然而,下面这段代码
1 2 3
   | const obj = {}; const arr = reactive([obj]) console.log(arr.includes[obj])
  | 
 
includes内部的this指向的是代理对象arr,并且在获取数组元素时得到的也是代理对象,所以用原始对象obj去查找找不到,返回false,因此我们需要重写includes方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | const arrInstrumentations={}
  ['includes','indexOf','lastIndexOf'].forEach(method=>{     const originMethod=Array.prototype[method]     arrInstrumentations[method]=function(...args){                           const res=originMethod.apply(this,args)                  if(res===false){             res=originMethod.apply(this.raw,args)         }     }     return res; })
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12
   | get(target,key,receiver){                          if(key==='raw'){                 return target             }                          if(Array.isArray(target)&&arrInstrumentations.hasOwnProperty(key)){                 return Reflect.get(arrInstrumentations,key,receiver)             }     ... }     
  | 
 
3 push/pop/shift/unshift等方法
当调用数组的push方法时,即会读取数组length属性值也会设置数组length属性值,会导致两个独立的副作用函数相互影响,就像
1 2 3
   | const arr=reactive([]) effect=(()=>{arr.push(1)}) effect=(()=>{arr.push(1)})
   | 
 
会得到栈溢出的错误
分析:
- 第一个副作用函数执行,在该函数内,调用arr.push方法向数组中添加一个元素,调用数组push方法时会间接读取数组的length属性,所以第一个副作用函数执行完毕会与length属性建立响应联系
 
- 第二个副作用函数执行,同样,与length属性建立响应联系,同时调用arr.push会设置length属性,于是响应式系统尝试把与length有关的副作用函数全部取出执行,就包括第一个副作用函数,此时,第二个副作用函数还未执行完毕就去调用第一个副作用函数
 
- 第一个副作用函数再次执行,也会间接设置数组的length属性,于是响应系统又尝试把所以与length属性相关联娿副作用取出执行,其中包括第二个副作用函数
 
- 循环往复导致栈溢出
 
因此,我们可以通过屏蔽对length属性的读取,避免在它与副作用函数之间建立联系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   |  let shouldTrack = true ['push'].forEach(method=>{          const originMethod = Array.prototype[method]          arrInstrumentations[method] = function(...args){                  shouldTrack=false;         let res = originMethod.apply(this,args)                  shouldTrack=true         return res     } })
 
  | 
 
在执行默认行为之前先将shouldTrack置false,禁止追踪,当push方法默认行为执行完毕后,将shouldTrack还原为true,
1 2 3 4 5
   | function track(target,key){          if(!activeEffect || !shouldTrack)return      ... }
  | 
 
当push方法间接读取length属性,由于此时是禁止追踪状态,所以length属性与副作用函数之间不会建立响应联系,也就不会产生栈溢出