Appearance
数组的拦截
数组的读取
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
}
})