数组
- key(索引) < target.length -> 操作类型为 SET, 正常通过索引修改属性值,和操作对象一样,无须特别处理
const arr = reactive([0])
effect(() => {
console.log(arr[0], 'effects run')
})
arr[0] = 1 // 触发响应
- 通过索引影响数组长度,key(索引) >= target.length -> 操作类型为 ADD 改变了数组的长度, trigger 需要对 length 属性进行特别处理
const arr = reactive([0])
effect(() => {
console.log(arr.length, 'effect run') // effect 访问 length 属性
})
arr[1] = 1 // 隐式修改了length,触发响应
// set时 判断操作类型, 当key(索引) < 数组长度时,操作类型为 SET 和第一种情况一样,
// 当 key(索引) >= 数组长度,操作类型为ADD,需要特别处理
const type = key < target.length ? 'SET' : 'ADD' //
// trigger 处理 需要手动获取 length的依赖然后触发
if (type === 'ADD' && Array.isArray(target)) {
const lengthEffects = depsMap.get('length')
lengthEffects &&
lengthEffects.forEach((effect) => {
if (effect !== 'activeEffect') {
effectsToRun.add(effect)
}
})
}
-
通过 修改 length 属性影响数组元素,数组元素索引 >= 新的 length, 需要特别处理, 数组元素索引 < 不需要处理
const arr = reactive([0]) arr.length = 100 // 不会影响 使用了arr[0]的地方,不需要特别处理 arr.length = 0 // 间接影响了使用arr[0] 的地方,需要特别处理 const arr = reactive([0]) effect(() => { console.log(arr[0], 'effects run') }) arr.length = 0 // 需要触发响应 // 源码 实现 if (Array.isArray(target) && key === 'length') { // 需要对 depsMap 遍历,而不是 effects,切记 // depsMap: (0: effects, 1: effects) 遍历的是 depsMap depsMap.forEach((effects, key) => { if (key >= newVal) { // key 是索引,索引 >= 新的length 的 effects 都要重新执行 effects.forEach((effect) => { if (effect !== activeEffect) { effectsToRun.add(effect) } }) } }) } -
对数组 for in, 本质上就是监听数组 length 属性的变化,在 ownKeys 里面使用 length 作为 key 去 track
// 添加新元素 影响for in
// 修改数组长度 影响 for in
const arr = reactive([0])
effect(() => {
for (let index in arr) {
}
console.log('effect run')
})
arr[1] = 1 // 触发响应式
arr.length = 2 // 触发响应式
// 源码实现
ownKeys(target) {
// 如果是数组,使用track时的key为length,对象是自定义的 ITERATE_KEY
track(target, Array.isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
}
- for of , 会读取数组的 index 属性 && length 属性
const arr = reactive([1, 2])
effect(() => {
for (let value of arr) {
}
console.log('effect run')
})
arr[1] = 2 // 触发响应,不用加额外的处理,for of 时数组的每个索引都添加了依赖
arr.length = 0 // 触发响应,不用加额外处理,for of 读取了length属性,length也保存了依赖25
-
proxyArr.includes(proxyArr[0]) 拿到的是 false
const obj = {} const proxyArr = reactive([obj]) proxyArr.includes(proxyArr[0]) // false // includes() 底层是通过索引访问对象,因为 proxyArr是一个reactive的对象,proxyArr[0]是对象,访问这个属性,会把这个属性值变为响应式的对象,相当于 obj = reactive(obj) // proxyArr[0] 自己访问时,也是因为相同的元素,也会再次创建一个 reactive(obj) // 两个不同的reactive对象,导致includes() 是false // 解决方法 reactive设置缓存对同一个对象只创建一个reactive对象 const reactiveMap = new Map() function reactive(obj) { // 一个对象已经是 reactive的,不会再次调用reactive 方法 const existionProxy = reactiveMap.get(target) if (existionProxy) return existionProxy const proxy = createReactive(obj) reactiveMap.set(obj, proxy) return proxy } -
proxyArr.includes(origin) 拿到的也是 false
数组 includes, lastIndexOf,indexOf 底层都会通过索引去访问数组的每个成员
const obj = {}
const proxyArr = reactive([obj])
proxyArr.includes(obj) // falss 同样的代理,底层includes()拿到的是proxy对象,proxy对象 !== obj, 所以返回false
// 解决方法,拦截 includes | lastIndexOf | indexOf 三个方法,
// 拦截器,拦截数组 includes lastIndexOf,indexOf 三个方法
const arrayInstrumentations = {}
;['includes', 'indexOf', 'lastIndexOf'].forEach((method) => {
const originMethod = Array.prototype[method] // 原方法
arrayInstrumentations[method] = function (...args) {
// 通过 Reflect.get(arrayInstrumetations, key, reciver) 来获取的,这个函数的this 指向 proxy
let res = originMethod.apply(this, args) // 当this为proxy 对象时,如果获取到的结果为false,需要再次使用原对象去获取结果
if (res === false) {
res = originMethod.apply(this.raw, args) // 原对象
}
return res
}
})
get(target,key,receiver) {
// 省略部分代码
// 拦截 includes | lastIndexOf | indexOf 三个方法,外部是通过 proxyArr.includes 调用的
if (Array.isArray(target) && arrayInstrumentations[key]) {
return Reflect.get(arrayInstrumentations,key,reciver)
}
}
- ['push', 'pop', 'shift', 'unshift', 'splice'] 五个方法 即会读取 length 属性,也会设置 length 属性,导致 track & trigger 一起触发,隐式修改数组长度,并且读取 length 属性的等等方法
['push', 'pop', 'shift', 'unshift', 'splice']
解决方法:push等等方法底层 读取 length 时,将 shouldTrack 设为 false,不允许 track
// 第一个effect track & trigger 正常操作
// 第二个effect 执行过程中,会trigger,导致第一个effect也执行,第一个effect也会trigger,导致第二个effect又执行,又导致第一个effect执行,造成栈溢出
effect(() => {
arr.push(1)
})
effect(() => {
arr.push(1)
})
let shouldTrack = true
function enableTracking() {
shouldTrack = true
}
function pauseTracking() {
shouldTrack = false
}
;[
// 拦截push 方法
'push',
].forEach((method) => {
const originMethod = Array.prototype[method]
arrayInstrumentations[method] = function (...args) {
pauseTracking() // 将 shouldTrack设为false
let res = originMethod.apply(this, args)
enableTracking() // 执行完之后 还原shouldTrack
return res
}
})
// 源码实现
function track(target, key) {
if (!activeEffect || !shouldTrack) return
}