2024.05.14
vue 响应式实现(3)
数组的拦截
数组的读取
const obj = {
a: 1,
b: 2,
c: {
aa: 11,
bb: 22
}
}
const arr = [1, obj, 3]
const proxyArr = reactive(arr)
✅ 没问题的情况:
proxyArr[0]
proxyArr.length
for (const key in proxyArr) {
proxyArr[key]
}
proxyArr.includes(3) // true
❌ 错误的结果:
proxyArr.includes(obj) // false
情况不对劲,分析一下
// 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,同时改写数组的查询方法
数组的 写入
✅ 没问题的情况:
proxyArr[0] = 10
proxyArr.length = 1
❌ 错误的结果:
proxy[10] = 10 // 没有更改 length 属性
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
}
但是这样的话,在删除的时候不能触发拦截
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
}
proxyArr.push(1)
proxyArr.push(2)
这样的话会一直触发依赖收集 这就需要增加自定义操作,选择是否触发依赖的收集
// effect/track.js
/**
* 依赖收集的触发暂停
*/
let shouldTrack = trueexport function pauseTracking() { shouldTrack = false}export function enableTracking() { shouldTrack = true}export function track(traget, type, key) { if (!shouldTrack)
return}// behover/index.js
import { enableTracking, pauseTracking } 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 }})