代理 Set 和 Map 的基本实现与实现代理 Set 和 Map 的响应联系
集合类型指 Set、Map、WeakSet 以及 WeakMap 。集合类型不同于普通对象,它有特定的数据操作方法。当使用 Proxy 代理集合类型的数据时要格外注意,例如,集合类型的 size 属性是一个访问器属性,当通过代理对象访问 size 属性时,由于代理对象本身并没有部署 [[SetData]] 这样的内部槽,所以会发生错误。另外,通过代理对象执行集合类型的操作方法时,要注意这些方法执行时的 this 指向,我们需要在 get 拦截函数内通过 .bind 函数为这些方法绑定正确的 this 值。同时,为了实现集合类型响应式数据,我们需要通过"重写"集合方法的方式来实现自定义的能力,当 Set 集合的 add 方法执行时,需要调用 trigger 函数触发响应。
代理 Set 和 Map 的基本实现的
js
// 存储副作用函数的桶
const bucket = new WeakMap()
const ITERATE_KEY = Symbol()
const arrayInstrumentations = {}
;['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
const originMethod = Array.prototype[method]
arrayInstrumentations[method] = function(...args) {
// this 是代理对象,先在代理对象中查找,将结果存储到 res 中
let res = originMethod.apply(this, args)
if (res === false || res === -1) {
// res 为 false 说明没找到,通过 this.raw 拿到原始数组,再去其中查找,并更新 res 值
res = originMethod.apply(this.raw, args)
}
// 返回最终结果
return res
}
})
// 一个标记变量,代表是否进行追踪。默认值为 true,即允许追踪
let shouldTrack = true
// 重写数组的 push、pop、shift、unshift 以及 splice 方法
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
// 取得原始 push 方法
const originMethod = Array.prototype[method]
// 重写
arrayInstrumentations[method] = function(...args) {
// 在调用原始方法之前,禁止追踪
shouldTrack = false
// push 方法的默认行为
let res = originMethod.apply(this, args)
// 在调用原始方法之后,恢复原来的行为,即允许追踪
shouldTrack = true
return res
}
})
// 增加第三个参数 isReadonly,代表是否只读,默认为 false,即非只读
function createReactive(obj, isShallow = false, isReadonly = false) {
return new Proxy(obj, {
deleteProperty(target, key) {
// 如果是只读的,则打印警告信息并返回
if (isReadonly) {
console.warn(`属性 ${key} 是只读的`)
return true
}
// 检查被操作的属性是否是对象自己的属性
const hadKey = Object.prototype.hasOwnProperty.call(target, key)
// 使用 Reflect.deleteProperty 完成属性的删除
const res = Reflect.deleteProperty(target, key)
if (res && hadKey) {
// 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新
trigger(target, key, 'DELETE')
}
return res
},
ownKeys(target) {
// 如果操作目标 target 是数组,则使用 length 属性作为 key 并建立响应联系
track(target, Array.isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
},
// 拦截读取操作
get(target, key, receiver) {
if (key === 'size') {
return Reflect.get(target, key, target)
}
return target[key].bind(target)
},
// 拦截设置操作
set(target, key, newVal, receiver) {
// 如果是只读的,则打印警告信息并返回
if (isReadonly) {
console.warn(`属性 ${key} 是只读的`)
return true
}
// 先获取旧值
const oldVal = target[key]
// 如果属性不存在,则说明是在添加新属性,否则是设置已有属性
const type = Array.isArray(target)
// 如果代理目标是数组,则检测被设置的索引值是否小于数组长度,
// 如果是,则视作 SET 操作,否则是 ADD 操作
? Number(key) < target.length ? 'SET' : 'ADD'
: Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
// 设置属性值
const res = Reflect.set(target, key, newVal, receiver)
// target === receiver.raw 说明 receiver 就是 target 的代理对象
if (target === receiver.raw) {
if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
// 增加第四个参数,即触发响应的新值
trigger(target, key, type, newVal)
}
}
return res
},
has(target, key) {
console.error('拦截 in 操作符')
track(target, key)
return Reflect.has(target, key)
}
})
}
// 定义一个 Map 实例,存储原始对象到代理对象的映射
const reactiveMap = new Map()
function reactive(obj) {
// 优先通过原始对象 obj 寻找之前创建的代理对象,如果找到了,直接返回已有的代理对象
const existionProxy = reactiveMap.get(obj)
if (existionProxy) return existionProxy
// 否则,创建新的代理对象
const proxy = createReactive(obj)
// 存储到 Map 中,从而避免重复创建
reactiveMap.set(obj, proxy)
return proxy
}
function shallowReactive(obj) {
return createReactive(obj, true)
}
function readonly(obj) {
return createReactive(obj, false, true /* 只读 */)
}
function shallowReadonly(obj) {
return createReactive(obj, true /* shallow */, true)
}
// 在 get 拦截函数内调用 track 函数追踪变化
function track(target, key) {
// 当禁止追踪时,直接返回
if (!activeEffect || !shouldTrack) return
let depsMap = bucket.get(target)
if (!depsMap) {
bucket.set(target, (depsMap = new Map()))
}
let deps = depsMap.get(key)
if (!deps) {
depsMap.set(key, (deps = new Set()))
}
// 把当前激活的副作用函数添加到依赖集合 deps 中
deps.add(activeEffect)
// deps 就是一个与当前副作用函数存在联系的依赖集合
// 将其添加到 activeEffect.deps 数组中
activeEffect.deps.push(deps)
}
// 为 trigger 函数增加第四个参数,newVal,即新值
function trigger(target, key, type, newVal) {
const depsMap = bucket.get(target)
if (!depsMap) return
// 取得与 key 相关联的副作用函数
const effects = depsMap.get(key)
const effectsToRun = new Set()
// 将与 key 相关联的副作用函数添加到 effectsToRun
effects && effects.forEach(effectFn => {
// 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
if (effectFn !== activeEffect) { // 新增
effectsToRun.add(effectFn)
}
})
// 当操作类型为 ADD 并且目标对象是数组时,应该取出并执行那些与 length 属性相关联的副作用函数
if (type === 'ADD' && Array.isArray(target)) {
// 取出与 length 相关联的副作用函数
const lengthEffects = depsMap.get('length')
// 将这些副作用函数添加到 effectsToRun 中,待执行
lengthEffects && lengthEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
// 如果操作目标是数组,并且修改了数组的 length 属性
if (Array.isArray(target) && key === 'length') {
// 对于索引大于或等于新的 length 值的元素,
// 需要把所有相关联的副作用函数取出并添加到 effectsToRun 中待执行
depsMap.forEach((effects, key) => {
if (key >= newVal) {
effects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
})
}
// 当操作类型为 ADD 或 DELETE 时,需要触发与 ITERATE_KEY 相关联的副作用函数重新执行
if (type === 'ADD' || type === 'DELETE') {
// 取得与 ITERATE_KEY 相关联的副作用函数
const iterateEffects = depsMap.get(ITERATE_KEY)
// 将与 ITERATE_KEY 相关联的副作用函数也添加到 effectsToRun
iterateEffects && iterateEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
effectsToRun.forEach(effectFn => {
if (effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn)
} else {
effectFn()
}
})
}
function cleanup(effectFn) {
// 遍历 effectFn.deps 数组
for (let i = 0; i < effectFn.deps.length; i++) {
// deps 是依赖集合
const deps = effectFn.deps[i]
// 将 effectFn 从依赖集合中移除
deps.delete(effectFn)
}
// 最后需要重置 effectFn.deps 数组
effectFn.deps.length = 0
}
// 用一个全局变量存储当前激活的 effect 函数
let activeEffect
// effect 栈
const effectStack = []
// effect 函数用于注册副作用函数
function effect(fn, options = {}) {
const effectFn = () => {
// 调用 cleanup 函数完成清除工作
cleanup(effectFn)
// 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect
activeEffect = effectFn
// 在调用副作用函数之前将当前副作用函数压入栈中
effectStack.push(effectFn)
fn()
// 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并把 activeEffect 还原为之前的值
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
// 将 options 挂载到 effectFn 上
effectFn.options = options
// activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
effectFn.deps = []
// 执行副作用函数
effectFn()
}
const p = reactive(new Set([1, 2, 3]))
console.log(p.size) // 3
p.delete(1)
实现代理 Set 和 Map 的响应联系
js
// 存储副作用函数的桶
const bucket = new WeakMap()
const ITERATE_KEY = Symbol()
const arrayInstrumentations = {}
;['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
const originMethod = Array.prototype[method]
arrayInstrumentations[method] = function(...args) {
// this 是代理对象,先在代理对象中查找,将结果存储到 res 中
let res = originMethod.apply(this, args)
if (res === false || res === -1) {
// res 为 false 说明没找到,通过 this.raw 拿到原始数组,再去其中查找,并更新 res 值
res = originMethod.apply(this.raw, args)
}
// 返回最终结果
return res
}
})
// 一个标记变量,代表是否进行追踪。默认值为 true,即允许追踪
let shouldTrack = true
// 重写数组的 push、pop、shift、unshift 以及 splice 方法
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
// 取得原始 push 方法
const originMethod = Array.prototype[method]
// 重写
arrayInstrumentations[method] = function(...args) {
// 在调用原始方法之前,禁止追踪
shouldTrack = false
// push 方法的默认行为
let res = originMethod.apply(this, args)
// 在调用原始方法之后,恢复原来的行为,即允许追踪
shouldTrack = true
return res
}
})
// 定义一个对象,将自定义的 add 方法定义到该对象下
const mutableInstrumentations = {
add(key) {
// this 仍然指向的是代理对象,通过 raw 属性获取原始数据对象
const target = this.raw
// 先判断值是否已经存在
const hadKey = target.has(key)
const res = target.add(key)
// 只有在值不存在的情况下,才需要触发响应
if (!hadKey) {
trigger(target, key, 'ADD')
}
// 返回操作结果
return res
},
delete(key) {
const target = this.raw
const hadKey = target.has(key)
const res = target.delete(key)
// 当要删除的元素确实存在时,才触发响应
if (hadKey) {
trigger(target, key, 'DELETE')
}
return res
}
}
// 增加第三个参数 isReadonly,代表是否只读,默认为 false,即非只读
function createReactive(obj, isShallow = false, isReadonly = false) {
return new Proxy(obj, {
deleteProperty(target, key) {
// 如果是只读的,则打印警告信息并返回
if (isReadonly) {
console.warn(`属性 ${key} 是只读的`)
return true
}
// 检查被操作的属性是否是对象自己的属性
const hadKey = Object.prototype.hasOwnProperty.call(target, key)
// 使用 Reflect.deleteProperty 完成属性的删除
const res = Reflect.deleteProperty(target, key)
if (res && hadKey) {
// 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新
trigger(target, key, 'DELETE')
}
return res
},
ownKeys(target) {
// 如果操作目标 target 是数组,则使用 length 属性作为 key 并建立响应联系
track(target, Array.isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
},
// 拦截读取操作
get(target, key, receiver) {
// 如果读取的是 raw 属性,则返回原始数据对象 target
if (key === 'raw') return target
if (key === 'size') {
// 调用 track 函数建立响应联系
track(target, ITERATE_KEY)
return Reflect.get(target, key, target)
}
// 返回定义在 mutableInstrumentations 对象下的方法
return mutableInstrumentations[key]
},
// 拦截设置操作
set(target, key, newVal, receiver) {
// 如果是只读的,则打印警告信息并返回
if (isReadonly) {
console.warn(`属性 ${key} 是只读的`)
return true
}
// 先获取旧值
const oldVal = target[key]
// 如果属性不存在,则说明是在添加新属性,否则是设置已有属性
const type = Array.isArray(target)
// 如果代理目标是数组,则检测被设置的索引值是否小于数组长度,
// 如果是,则视作 SET 操作,否则是 ADD 操作
? Number(key) < target.length ? 'SET' : 'ADD'
: Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
// 设置属性值
const res = Reflect.set(target, key, newVal, receiver)
// target === receiver.raw 说明 receiver 就是 target 的代理对象
if (target === receiver.raw) {
if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
// 增加第四个参数,即触发响应的新值
trigger(target, key, type, newVal)
}
}
return res
},
has(target, key) {
console.error('拦截 in 操作符')
track(target, key)
return Reflect.has(target, key)
}
})
}
// 定义一个 Map 实例,存储原始对象到代理对象的映射
const reactiveMap = new Map()
function reactive(obj) {
// 优先通过原始对象 obj 寻找之前创建的代理对象,如果找到了,直接返回已有的代理对象
const existionProxy = reactiveMap.get(obj)
if (existionProxy) return existionProxy
// 否则,创建新的代理对象
const proxy = createReactive(obj)
// 存储到 Map 中,从而避免重复创建
reactiveMap.set(obj, proxy)
return proxy
}
function shallowReactive(obj) {
return createReactive(obj, true)
}
function readonly(obj) {
return createReactive(obj, false, true /* 只读 */)
}
function shallowReadonly(obj) {
return createReactive(obj, true /* shallow */, true)
}
// 在 get 拦截函数内调用 track 函数追踪变化
function track(target, key) {
// 当禁止追踪时,直接返回
if (!activeEffect || !shouldTrack) return
let depsMap = bucket.get(target)
if (!depsMap) {
bucket.set(target, (depsMap = new Map()))
}
let deps = depsMap.get(key)
if (!deps) {
depsMap.set(key, (deps = new Set()))
}
// 把当前激活的副作用函数添加到依赖集合 deps 中
deps.add(activeEffect)
// deps 就是一个与当前副作用函数存在联系的依赖集合
// 将其添加到 activeEffect.deps 数组中
activeEffect.deps.push(deps)
}
// 为 trigger 函数增加第四个参数,newVal,即新值
function trigger(target, key, type, newVal) {
const depsMap = bucket.get(target)
if (!depsMap) return
// 取得与 key 相关联的副作用函数
const effects = depsMap.get(key)
const effectsToRun = new Set()
// 将与 key 相关联的副作用函数添加到 effectsToRun
effects && effects.forEach(effectFn => {
// 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
if (effectFn !== activeEffect) { // 新增
effectsToRun.add(effectFn)
}
})
// 当操作类型为 ADD 并且目标对象是数组时,应该取出并执行那些与 length 属性相关联的副作用函数
if (type === 'ADD' && Array.isArray(target)) {
// 取出与 length 相关联的副作用函数
const lengthEffects = depsMap.get('length')
// 将这些副作用函数添加到 effectsToRun 中,待执行
lengthEffects && lengthEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
// 如果操作目标是数组,并且修改了数组的 length 属性
if (Array.isArray(target) && key === 'length') {
// 对于索引大于或等于新的 length 值的元素,
// 需要把所有相关联的副作用函数取出并添加到 effectsToRun 中待执行
depsMap.forEach((effects, key) => {
if (key >= newVal) {
effects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
})
}
// 当操作类型为 ADD 或 DELETE 时,需要触发与 ITERATE_KEY 相关联的副作用函数重新执行
if (type === 'ADD' || type === 'DELETE') {
// 取得与 ITERATE_KEY 相关联的副作用函数
const iterateEffects = depsMap.get(ITERATE_KEY)
// 将与 ITERATE_KEY 相关联的副作用函数也添加到 effectsToRun
iterateEffects && iterateEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
effectsToRun.forEach(effectFn => {
if (effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn)
} else {
effectFn()
}
})
}
function cleanup(effectFn) {
// 遍历 effectFn.deps 数组
for (let i = 0; i < effectFn.deps.length; i++) {
// deps 是依赖集合
const deps = effectFn.deps[i]
// 将 effectFn 从依赖集合中移除
deps.delete(effectFn)
}
// 最后需要重置 effectFn.deps 数组
effectFn.deps.length = 0
}
// 用一个全局变量存储当前激活的 effect 函数
let activeEffect
// effect 栈
const effectStack = []
// effect 函数用于注册副作用函数
function effect(fn, options = {}) {
const effectFn = () => {
// 调用 cleanup 函数完成清除工作
cleanup(effectFn)
// 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect
activeEffect = effectFn
// 在调用副作用函数之前将当前副作用函数压入栈中
effectStack.push(effectFn)
fn()
// 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并把 activeEffect 还原为之前的值
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
// 将 options 挂载到 effectFn 上
effectFn.options = options
// activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
effectFn.deps = []
// 执行副作用函数
effectFn()
}
const p = reactive(new Set([1, 2, 3]))
effect(() => {
// 在副作用函数内访问 size 属性
console.log(p.size)
})
// 添加值为 4 的元素,应该触发响应
p.add(4)
代理 Set 和 Map 避免污染原始数据实现
我们把响应式数据设置到原始数据上的行为称为数据污染。它导致用户可以通过原始数据执行响应式相关操作。为了避免这类问题发生,我们通过响应式数据对象的 raw 属性来访问对应的原始数据对象,后续操作使用原始数据对象就可以了。
代理 Set 和 Map 避免污染原始数据的完整实现如下:
js
// 存储副作用函数的桶
const bucket = new WeakMap()
const ITERATE_KEY = Symbol()
const arrayInstrumentations = {}
;['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
const originMethod = Array.prototype[method]
arrayInstrumentations[method] = function(...args) {
// this 是代理对象,先在代理对象中查找,将结果存储到 res 中
let res = originMethod.apply(this, args)
if (res === false || res === -1) {
// res 为 false 说明没找到,通过 this.raw 拿到原始数组,再去其中查找,并更新 res 值
res = originMethod.apply(this.raw, args)
}
// 返回最终结果
return res
}
})
// 一个标记变量,代表是否进行追踪。默认值为 true,即允许追踪
let shouldTrack = true
// 重写数组的 push、pop、shift、unshift 以及 splice 方法
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
// 取得原始 push 方法
const originMethod = Array.prototype[method]
// 重写
arrayInstrumentations[method] = function(...args) {
// 在调用原始方法之前,禁止追踪
shouldTrack = false
// push 方法的默认行为
let res = originMethod.apply(this, args)
// 在调用原始方法之后,恢复原来的行为,即允许追踪
shouldTrack = true
return res
}
})
// 定义一个对象,将自定义的 add 方法定义到该对象下
const mutableInstrumentations = {
get(key) {
// 获取原始对象
const target = this.raw
// 判断读取的 key 是否存在
const had = target.has(key)
// 追踪依赖,建立响应联系
track(target, key)
// 如果存在,则返回结果。这里要注意的是,如果得到的结果 res 仍然是可代理的数据,
// 则要返回使用 reactive 包装后的响应式数据
if (had) {
const res = target.get(key)
return typeof res === 'object' ? reactive(res) : res
}
},
set(key, value) {
const target = this.raw
const had = target.has(key)
// 获取旧值
const oldValue = target.get(key)
// 获取原始数据,由于 value 本身可能已经是原始数据,所以此时 value.raw 不存在,则直接使用 value
const rawValue = value.raw || value
target.set(key, rawValue)
// 如果不存在,则说明是 ADD 类型的操作,意味着新增
if (!had) {
trigger(target, key, 'ADD')
} else if (oldValue !== value || (oldValue === oldValue && value === value)) {
// 如果不存在,并且值变了,则是 SET 类型的操作,意味着修改
trigger(target, key, 'SET')
}
},
add(key) {
// this 仍然指向的是代理对象,通过 raw 属性获取原始数据对象
const target = this.raw
// 先判断值是否已经存在
const hadKey = target.has(key)
const res = target.add(key)
// 只有在值不存在的情况下,才需要触发响应
if (!hadKey) {
trigger(target, key, 'ADD')
}
// 返回操作结果
return res
},
delete(key) {
const target = this.raw
const hadKey = target.has(key)
const res = target.delete(key)
// 当要删除的元素确实存在时,才触发响应
if (hadKey) {
trigger(target, key, 'DELETE')
}
return res
}
}
// 增加第三个参数 isReadonly,代表是否只读,默认为 false,即非只读
function createReactive(obj, isShallow = false, isReadonly = false) {
return new Proxy(obj, {
deleteProperty(target, key) {
// 如果是只读的,则打印警告信息并返回
if (isReadonly) {
console.warn(`属性 ${key} 是只读的`)
return true
}
// 检查被操作的属性是否是对象自己的属性
const hadKey = Object.prototype.hasOwnProperty.call(target, key)
// 使用 Reflect.deleteProperty 完成属性的删除
const res = Reflect.deleteProperty(target, key)
if (res && hadKey) {
// 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新
trigger(target, key, 'DELETE')
}
return res
},
ownKeys(target) {
// 如果操作目标 target 是数组,则使用 length 属性作为 key 并建立响应联系
track(target, Array.isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
},
// 拦截读取操作
get(target, key, receiver) {
// 如果读取的是 raw 属性,则返回原始数据对象 target
if (key === 'raw') return target
if (key === 'size') {
// 调用 track 函数建立响应联系
track(target, ITERATE_KEY)
return Reflect.get(target, key, target)
}
// 返回定义在 mutableInstrumentations 对象下的方法
return mutableInstrumentations[key]
},
// 拦截设置操作
set(target, key, newVal, receiver) {
// 如果是只读的,则打印警告信息并返回
if (isReadonly) {
console.warn(`属性 ${key} 是只读的`)
return true
}
// 先获取旧值
const oldVal = target[key]
// 如果属性不存在,则说明是在添加新属性,否则是设置已有属性
const type = Array.isArray(target)
// 如果代理目标是数组,则检测被设置的索引值是否小于数组长度,
// 如果是,则视作 SET 操作,否则是 ADD 操作
? Number(key) < target.length ? 'SET' : 'ADD'
: Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
// 设置属性值
const res = Reflect.set(target, key, newVal, receiver)
// target === receiver.raw 说明 receiver 就是 target 的代理对象
if (target === receiver.raw) {
if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
// 增加第四个参数,即触发响应的新值
trigger(target, key, type, newVal)
}
}
return res
},
has(target, key) {
console.error('拦截 in 操作符')
track(target, key)
return Reflect.has(target, key)
}
})
}
// 定义一个 Map 实例,存储原始对象到代理对象的映射
const reactiveMap = new Map()
function reactive(obj) {
// 优先通过原始对象 obj 寻找之前创建的代理对象,如果找到了,直接返回已有的代理对象
const existionProxy = reactiveMap.get(obj)
if (existionProxy) return existionProxy
// 否则,创建新的代理对象
const proxy = createReactive(obj)
// 存储到 Map 中,从而避免重复创建
reactiveMap.set(obj, proxy)
return proxy
}
function shallowReactive(obj) {
return createReactive(obj, true)
}
function readonly(obj) {
return createReactive(obj, false, true /* 只读 */)
}
function shallowReadonly(obj) {
return createReactive(obj, true /* shallow */, true)
}
// 在 get 拦截函数内调用 track 函数追踪变化
function track(target, key) {
// 当禁止追踪时,直接返回
if (!activeEffect || !shouldTrack) return
let depsMap = bucket.get(target)
if (!depsMap) {
bucket.set(target, (depsMap = new Map()))
}
let deps = depsMap.get(key)
if (!deps) {
depsMap.set(key, (deps = new Set()))
}
// 把当前激活的副作用函数添加到依赖集合 deps 中
deps.add(activeEffect)
// deps 就是一个与当前副作用函数存在联系的依赖集合
// 将其添加到 activeEffect.deps 数组中
activeEffect.deps.push(deps)
}
// 为 trigger 函数增加第四个参数,newVal,即新值
function trigger(target, key, type, newVal) {
const depsMap = bucket.get(target)
if (!depsMap) return
// 取得与 key 相关联的副作用函数
const effects = depsMap.get(key)
const effectsToRun = new Set()
// 将与 key 相关联的副作用函数添加到 effectsToRun
effects && effects.forEach(effectFn => {
// 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
if (effectFn !== activeEffect) { // 新增
effectsToRun.add(effectFn)
}
})
// 当操作类型为 ADD 并且目标对象是数组时,应该取出并执行那些与 length 属性相关联的副作用函数
if (type === 'ADD' && Array.isArray(target)) {
// 取出与 length 相关联的副作用函数
const lengthEffects = depsMap.get('length')
// 将这些副作用函数添加到 effectsToRun 中,待执行
lengthEffects && lengthEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
// 如果操作目标是数组,并且修改了数组的 length 属性
if (Array.isArray(target) && key === 'length') {
// 对于索引大于或等于新的 length 值的元素,
// 需要把所有相关联的副作用函数取出并添加到 effectsToRun 中待执行
depsMap.forEach((effects, key) => {
if (key >= newVal) {
effects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
})
}
// 当操作类型为 ADD 或 DELETE 时,需要触发与 ITERATE_KEY 相关联的副作用函数重新执行
if (type === 'ADD' || type === 'DELETE') {
// 取得与 ITERATE_KEY 相关联的副作用函数
const iterateEffects = depsMap.get(ITERATE_KEY)
// 将与 ITERATE_KEY 相关联的副作用函数也添加到 effectsToRun
iterateEffects && iterateEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
effectsToRun.forEach(effectFn => {
if (effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn)
} else {
effectFn()
}
})
}
function cleanup(effectFn) {
// 遍历 effectFn.deps 数组
for (let i = 0; i < effectFn.deps.length; i++) {
// deps 是依赖集合
const deps = effectFn.deps[i]
// 将 effectFn 从依赖集合中移除
deps.delete(effectFn)
}
// 最后需要重置 effectFn.deps 数组
effectFn.deps.length = 0
}
// 用一个全局变量存储当前激活的 effect 函数
let activeEffect
// effect 栈
const effectStack = []
// effect 函数用于注册副作用函数
function effect(fn, options = {}) {
const effectFn = () => {
// 调用 cleanup 函数完成清除工作
cleanup(effectFn)
// 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect
activeEffect = effectFn
// 在调用副作用函数之前将当前副作用函数压入栈中
effectStack.push(effectFn)
fn()
// 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并把 activeEffect 还原为之前的值
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
// 将 options 挂载到 effectFn 上
effectFn.options = options
// activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
effectFn.deps = []
// 执行副作用函数
effectFn()
}
// 原始 Map 对象 m
const m = new Map()
// p1 是 m 的代理对象
const p1 = reactive(m)
// p2 是另外一个代理对象
const p2 = reactive(new Map())
// 为 p1 设置一个键值对,值是代理对象 p2
p1.set('p2', p2)
effect(() => {
// 注意,这里我们通过原始数据 m 访问 p2
console.log(m.get('p2').size)
})
// 注意,这里我们通过原始数据 m 为 p2 设置一个键值对 foo --> 1
m.get('p2').set('foo', 1)
代理 Set 和 Map forEach 遍历的实现
集合的 forEach 方法与对象的 for...in 遍历类似,最大的不同体现在,当使用 for...in 遍历对象时,我们只关心对象的键是否变化,而不关心值;但使用 forEach 遍历集合时,我们既关心键的变化,也关心值的变化。
代理 Set 和 Map forEach 遍历的实现的完整实现如下:
js
// 存储副作用函数的桶
const bucket = new WeakMap()
const ITERATE_KEY = Symbol()
const arrayInstrumentations = {}
;['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
const originMethod = Array.prototype[method]
arrayInstrumentations[method] = function(...args) {
// this 是代理对象,先在代理对象中查找,将结果存储到 res 中
let res = originMethod.apply(this, args)
if (res === false || res === -1) {
// res 为 false 说明没找到,通过 this.raw 拿到原始数组,再去其中查找,并更新 res 值
res = originMethod.apply(this.raw, args)
}
// 返回最终结果
return res
}
})
// 一个标记变量,代表是否进行追踪。默认值为 true,即允许追踪
let shouldTrack = true
// 重写数组的 push、pop、shift、unshift 以及 splice 方法
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
// 取得原始 push 方法
const originMethod = Array.prototype[method]
// 重写
arrayInstrumentations[method] = function(...args) {
// 在调用原始方法之前,禁止追踪
shouldTrack = false
// push 方法的默认行为
let res = originMethod.apply(this, args)
// 在调用原始方法之后,恢复原来的行为,即允许追踪
shouldTrack = true
return res
}
})
// 定义一个对象,将自定义的 add 方法定义到该对象下
const mutableInstrumentations = {
get(key) {
// 获取原始对象
const target = this.raw
// 判断读取的 key 是否存在
const had = target.has(key)
// 追踪依赖,建立响应联系
track(target, key)
// 如果存在,则返回结果。这里要注意的是,如果得到的结果 res 仍然是可代理的数据,
// 则要返回使用 reactive 包装后的响应式数据
if (had) {
const res = target.get(key)
return typeof res === 'object' ? reactive(res) : res
}
},
set(key, value) {
const target = this.raw
const had = target.has(key)
// 获取旧值
const oldValue = target.get(key)
// 获取原始数据,由于 value 本身可能已经是原始数据,所以此时 value.raw 不存在,则直接使用 value
const rawValue = value.raw || value
target.set(key, rawValue)
// 如果不存在,则说明是 ADD 类型的操作,意味着新增
if (!had) {
trigger(target, key, 'ADD')
} else if (oldValue !== value || (oldValue === oldValue && value === value)) {
// 如果不存在,并且值变了,则是 SET 类型的操作,意味着修改
trigger(target, key, 'SET')
}
},
add(key) {
// this 仍然指向的是代理对象,通过 raw 属性获取原始数据对象
const target = this.raw
// 先判断值是否已经存在
const hadKey = target.has(key)
const res = target.add(key)
// 只有在值不存在的情况下,才需要触发响应
if (!hadKey) {
trigger(target, key, 'ADD')
}
// 返回操作结果
return res
},
delete(key) {
const target = this.raw
const hadKey = target.has(key)
const res = target.delete(key)
// 当要删除的元素确实存在时,才触发响应
if (hadKey) {
trigger(target, key, 'DELETE')
}
return res
},
// 接收第二个参数
forEach(callback, thisArg) {
// wrap 函数用来把可代理的值转换为响应式数据
const wrap = (val) => typeof val === 'object' ? reactive(val) : val
// 取得原始数据对象
const target = this.raw
// 与 ITERATE_KEY 建立响应联系
track(target, ITERATE_KEY)
// 通过 target 调用原始 forEach 方法进行遍历
target.forEach((v, k) => {
// 通过 .call 调用 callback,并传递 thisArg
callback.call(thisArg, wrap(v), wrap(k), this)
})
}
}
// 增加第三个参数 isReadonly,代表是否只读,默认为 false,即非只读
function createReactive(obj, isShallow = false, isReadonly = false) {
return new Proxy(obj, {
deleteProperty(target, key) {
// 如果是只读的,则打印警告信息并返回
if (isReadonly) {
console.warn(`属性 ${key} 是只读的`)
return true
}
// 检查被操作的属性是否是对象自己的属性
const hadKey = Object.prototype.hasOwnProperty.call(target, key)
// 使用 Reflect.deleteProperty 完成属性的删除
const res = Reflect.deleteProperty(target, key)
if (res && hadKey) {
// 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新
trigger(target, key, 'DELETE')
}
return res
},
ownKeys(target) {
// 如果操作目标 target 是数组,则使用 length 属性作为 key 并建立响应联系
track(target, Array.isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
},
// 拦截读取操作
get(target, key, receiver) {
// 如果读取的是 raw 属性,则返回原始数据对象 target
if (key === 'raw') return target
if (key === 'size') {
// 调用 track 函数建立响应联系
track(target, ITERATE_KEY)
return Reflect.get(target, key, target)
}
// 返回定义在 mutableInstrumentations 对象下的方法
return mutableInstrumentations[key]
},
// 拦截设置操作
set(target, key, newVal, receiver) {
// 如果是只读的,则打印警告信息并返回
if (isReadonly) {
console.warn(`属性 ${key} 是只读的`)
return true
}
// 先获取旧值
const oldVal = target[key]
// 如果属性不存在,则说明是在添加新属性,否则是设置已有属性
const type = Array.isArray(target)
// 如果代理目标是数组,则检测被设置的索引值是否小于数组长度,
// 如果是,则视作 SET 操作,否则是 ADD 操作
? Number(key) < target.length ? 'SET' : 'ADD'
: Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
// 设置属性值
const res = Reflect.set(target, key, newVal, receiver)
// target === receiver.raw 说明 receiver 就是 target 的代理对象
if (target === receiver.raw) {
if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
// 增加第四个参数,即触发响应的新值
trigger(target, key, type, newVal)
}
}
return res
},
has(target, key) {
console.error('拦截 in 操作符')
track(target, key)
return Reflect.has(target, key)
}
})
}
// 定义一个 Map 实例,存储原始对象到代理对象的映射
const reactiveMap = new Map()
function reactive(obj) {
// 优先通过原始对象 obj 寻找之前创建的代理对象,如果找到了,直接返回已有的代理对象
const existionProxy = reactiveMap.get(obj)
if (existionProxy) return existionProxy
// 否则,创建新的代理对象
const proxy = createReactive(obj)
// 存储到 Map 中,从而避免重复创建
reactiveMap.set(obj, proxy)
return proxy
}
function shallowReactive(obj) {
return createReactive(obj, true)
}
function readonly(obj) {
return createReactive(obj, false, true /* 只读 */)
}
function shallowReadonly(obj) {
return createReactive(obj, true /* shallow */, true)
}
// 在 get 拦截函数内调用 track 函数追踪变化
function track(target, key) {
// 当禁止追踪时,直接返回
if (!activeEffect || !shouldTrack) return
let depsMap = bucket.get(target)
if (!depsMap) {
bucket.set(target, (depsMap = new Map()))
}
let deps = depsMap.get(key)
if (!deps) {
depsMap.set(key, (deps = new Set()))
}
// 把当前激活的副作用函数添加到依赖集合 deps 中
deps.add(activeEffect)
// deps 就是一个与当前副作用函数存在联系的依赖集合
// 将其添加到 activeEffect.deps 数组中
activeEffect.deps.push(deps)
}
// 为 trigger 函数增加第四个参数,newVal,即新值
function trigger(target, key, type, newVal) {
const depsMap = bucket.get(target)
if (!depsMap) return
// 取得与 key 相关联的副作用函数
const effects = depsMap.get(key)
const effectsToRun = new Set()
// 将与 key 相关联的副作用函数添加到 effectsToRun
effects && effects.forEach(effectFn => {
// 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
if (effectFn !== activeEffect) { // 新增
effectsToRun.add(effectFn)
}
})
// 当操作类型为 ADD 并且目标对象是数组时,应该取出并执行那些与 length 属性相关联的副作用函数
if (type === 'ADD' && Array.isArray(target)) {
// 取出与 length 相关联的副作用函数
const lengthEffects = depsMap.get('length')
// 将这些副作用函数添加到 effectsToRun 中,待执行
lengthEffects && lengthEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
// 如果操作目标是数组,并且修改了数组的 length 属性
if (Array.isArray(target) && key === 'length') {
// 对于索引大于或等于新的 length 值的元素,
// 需要把所有相关联的副作用函数取出并添加到 effectsToRun 中待执行
depsMap.forEach((effects, key) => {
if (key >= newVal) {
effects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
})
}
// 当操作类型为 ADD 或 DELETE 时,需要触发与 ITERATE_KEY 相关联的副作用函数重新执行
if (
type === 'ADD' ||
type === 'DELETE' ||
// 如果操作类型是 SET,并且目标对象是 Map 类型的数据,
// 也应该触发那些与 ITERATE_KEY 相关联的副作用函数重新执行
(
type === 'SET' &&
Object.prototype.toString.call(target) === '[object Map]'
)
) {
// 取得与 ITERATE_KEY 相关联的副作用函数
const iterateEffects = depsMap.get(ITERATE_KEY)
// 将与 ITERATE_KEY 相关联的副作用函数也添加到 effectsToRun
iterateEffects && iterateEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
effectsToRun.forEach(effectFn => {
if (effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn)
} else {
effectFn()
}
})
}
function cleanup(effectFn) {
// 遍历 effectFn.deps 数组
for (let i = 0; i < effectFn.deps.length; i++) {
// deps 是依赖集合
const deps = effectFn.deps[i]
// 将 effectFn 从依赖集合中移除
deps.delete(effectFn)
}
// 最后需要重置 effectFn.deps 数组
effectFn.deps.length = 0
}
// 用一个全局变量存储当前激活的 effect 函数
let activeEffect
// effect 栈
const effectStack = []
// effect 函数用于注册副作用函数
function effect(fn, options = {}) {
const effectFn = () => {
// 调用 cleanup 函数完成清除工作
cleanup(effectFn)
// 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect
activeEffect = effectFn
// 在调用副作用函数之前将当前副作用函数压入栈中
effectStack.push(effectFn)
fn()
// 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并把 activeEffect 还原为之前的值
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
// 将 options 挂载到 effectFn 上
effectFn.options = options
// activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
effectFn.deps = []
// 执行副作用函数
effectFn()
}
const p = reactive(new Map([
['key', 1]
]))
effect(() => {
p.forEach(function (value, key) {
// forEach 循环不仅关心集合的键,还关心集合的值
console.log(value) // 1
})
})
p.set('key', 2) // 即使操作类型是 SET,也应该触发响应
代理 Set 和 Map 实现迭代器方法的响应性
一个对象能否迭代,取决于该对象是否实现了迭代协议。
-
可迭代协议指的是一个对象实现了 Symbol.iterator 方法
-
迭代器协议指的是一个对象实现了 next 方法
一个对象可以同时实现可迭代协议和迭代器协议,例如:
js
const obj = {
// 迭代器协议
next() {
// ...
}
// 可迭代协议
[Symbol.iterator]() {
return this
}
}
一个对象同时实现了可迭代协议和迭代器协议才可以被迭代。
实现集合类型的数据结构的迭代器方法的拦截,关键在于需要借助原生的可迭代协议与迭代器协议,实现自定义的可迭代协议与迭代器协议,调用 track 函数建立响应联系。
js
const mutableInstrumentations = {
[Symbol.iterator]: iterationMethod,
entries: iterationMethod
}
// 抽离为独立的函数,便于复用
function iterationMethod() {
// 获取原始数据对象 target
const target = this.raw
// 获取原始迭代器方法
const itr = target[Symbol.iterator]()
const wrap = (val) => typeof val === 'object' ? reactive(val) : val
// 调用 track 函数建立响应联系
track(target, ITERATE_KEY)
// 返回自定义的迭代器
return {
next() {
// 调用原始迭代器的 next 方法获取 value 和 done
const { value, done } = itr.next()
return {
// 如果 value 不是 undefined,则对其进行包裹
value: value ? [wrap(value[0]), wrap(value[1])] : value,
done
}
},
// 实现可迭代协议
[Symbol.iterator]() {
return this
}
}
}
代理 Set 和 Map 实现迭代器方法的响应性的完整实现如下:
js
// 存储副作用函数的桶
const bucket = new WeakMap()
const ITERATE_KEY = Symbol()
const arrayInstrumentations = {}
;['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
const originMethod = Array.prototype[method]
arrayInstrumentations[method] = function(...args) {
// this 是代理对象,先在代理对象中查找,将结果存储到 res 中
let res = originMethod.apply(this, args)
if (res === false || res === -1) {
// res 为 false 说明没找到,通过 this.raw 拿到原始数组,再去其中查找,并更新 res 值
res = originMethod.apply(this.raw, args)
}
// 返回最终结果
return res
}
})
// 一个标记变量,代表是否进行追踪。默认值为 true,即允许追踪
let shouldTrack = true
// 重写数组的 push、pop、shift、unshift 以及 splice 方法
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
// 取得原始 push 方法
const originMethod = Array.prototype[method]
// 重写
arrayInstrumentations[method] = function(...args) {
// 在调用原始方法之前,禁止追踪
shouldTrack = false
// push 方法的默认行为
let res = originMethod.apply(this, args)
// 在调用原始方法之后,恢复原来的行为,即允许追踪
shouldTrack = true
return res
}
})
// 定义一个对象,将自定义的 add 方法定义到该对象下
const mutableInstrumentations = {
get(key) {
// 获取原始对象
const target = this.raw
// 判断读取的 key 是否存在
const had = target.has(key)
// 追踪依赖,建立响应联系
track(target, key)
// 如果存在,则返回结果。这里要注意的是,如果得到的结果 res 仍然是可代理的数据,
// 则要返回使用 reactive 包装后的响应式数据
if (had) {
const res = target.get(key)
return typeof res === 'object' ? reactive(res) : res
}
},
set(key, value) {
const target = this.raw
const had = target.has(key)
// 获取旧值
const oldValue = target.get(key)
// 获取原始数据,由于 value 本身可能已经是原始数据,所以此时 value.raw 不存在,则直接使用 value
const rawValue = value.raw || value
target.set(key, rawValue)
// 如果不存在,则说明是 ADD 类型的操作,意味着新增
if (!had) {
trigger(target, key, 'ADD')
} else if (oldValue !== value || (oldValue === oldValue && value === value)) {
// 如果不存在,并且值变了,则是 SET 类型的操作,意味着修改
trigger(target, key, 'SET')
}
},
add(key) {
// this 仍然指向的是代理对象,通过 raw 属性获取原始数据对象
const target = this.raw
// 先判断值是否已经存在
const hadKey = target.has(key)
const res = target.add(key)
// 只有在值不存在的情况下,才需要触发响应
if (!hadKey) {
trigger(target, key, 'ADD')
}
// 返回操作结果
return res
},
delete(key) {
const target = this.raw
const hadKey = target.has(key)
const res = target.delete(key)
// 当要删除的元素确实存在时,才触发响应
if (hadKey) {
trigger(target, key, 'DELETE')
}
return res
},
// 接收第二个参数
forEach(callback, thisArg) {
// wrap 函数用来把可代理的值转换为响应式数据
const wrap = (val) => typeof val === 'object' ? reactive(val) : val
// 取得原始数据对象
const target = this.raw
// 与 ITERATE_KEY 建立响应联系
track(target, ITERATE_KEY)
// 通过 target 调用原始 forEach 方法进行遍历
target.forEach((v, k) => {
// 通过 .call 调用 callback,并传递 thisArg
callback.call(thisArg, wrap(v), wrap(k), this)
})
},
// 共用 iterationMethod 方法
[Symbol.iterator]: iterationMethod,
entries: iterationMethod
}
// 抽离为独立的函数,便于复用
function iterationMethod() {
// 获取原始数据对象 target
const target = this.raw
// 获取原始迭代器方法
const itr = target[Symbol.iterator]()
const wrap = (val) => typeof val === 'object' ? reactive(val) : val
// 调用 track 函数建立响应联系
track(target, ITERATE_KEY)
// 返回自定义的迭代器
return {
next() {
// 调用原始迭代器的 next 方法获取 value 和 done
const { value, done } = itr.next()
return {
// 如果 value 不是 undefined,则对其进行包裹
value: value ? [wrap(value[0]), wrap(value[1])] : value,
done
}
},
// 实现可迭代协议
[Symbol.iterator]() {
return this
}
}
}
// 增加第三个参数 isReadonly,代表是否只读,默认为 false,即非只读
function createReactive(obj, isShallow = false, isReadonly = false) {
return new Proxy(obj, {
deleteProperty(target, key) {
// 如果是只读的,则打印警告信息并返回
if (isReadonly) {
console.warn(`属性 ${key} 是只读的`)
return true
}
// 检查被操作的属性是否是对象自己的属性
const hadKey = Object.prototype.hasOwnProperty.call(target, key)
// 使用 Reflect.deleteProperty 完成属性的删除
const res = Reflect.deleteProperty(target, key)
if (res && hadKey) {
// 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新
trigger(target, key, 'DELETE')
}
return res
},
ownKeys(target) {
// 如果操作目标 target 是数组,则使用 length 属性作为 key 并建立响应联系
track(target, Array.isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
},
// 拦截读取操作
get(target, key, receiver) {
// 如果读取的是 raw 属性,则返回原始数据对象 target
if (key === 'raw') return target
if (key === 'size') {
// 调用 track 函数建立响应联系
track(target, ITERATE_KEY)
return Reflect.get(target, key, target)
}
// 返回定义在 mutableInstrumentations 对象下的方法
return mutableInstrumentations[key]
},
// 拦截设置操作
set(target, key, newVal, receiver) {
// 如果是只读的,则打印警告信息并返回
if (isReadonly) {
console.warn(`属性 ${key} 是只读的`)
return true
}
// 先获取旧值
const oldVal = target[key]
// 如果属性不存在,则说明是在添加新属性,否则是设置已有属性
const type = Array.isArray(target)
// 如果代理目标是数组,则检测被设置的索引值是否小于数组长度,
// 如果是,则视作 SET 操作,否则是 ADD 操作
? Number(key) < target.length ? 'SET' : 'ADD'
: Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
// 设置属性值
const res = Reflect.set(target, key, newVal, receiver)
// target === receiver.raw 说明 receiver 就是 target 的代理对象
if (target === receiver.raw) {
if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
// 增加第四个参数,即触发响应的新值
trigger(target, key, type, newVal)
}
}
return res
},
has(target, key) {
console.error('拦截 in 操作符')
track(target, key)
return Reflect.has(target, key)
}
})
}
// 定义一个 Map 实例,存储原始对象到代理对象的映射
const reactiveMap = new Map()
function reactive(obj) {
// 优先通过原始对象 obj 寻找之前创建的代理对象,如果找到了,直接返回已有的代理对象
const existionProxy = reactiveMap.get(obj)
if (existionProxy) return existionProxy
// 否则,创建新的代理对象
const proxy = createReactive(obj)
// 存储到 Map 中,从而避免重复创建
reactiveMap.set(obj, proxy)
return proxy
}
function shallowReactive(obj) {
return createReactive(obj, true)
}
function readonly(obj) {
return createReactive(obj, false, true /* 只读 */)
}
function shallowReadonly(obj) {
return createReactive(obj, true /* shallow */, true)
}
// 在 get 拦截函数内调用 track 函数追踪变化
function track(target, key) {
// 当禁止追踪时,直接返回
if (!activeEffect || !shouldTrack) return
let depsMap = bucket.get(target)
if (!depsMap) {
bucket.set(target, (depsMap = new Map()))
}
let deps = depsMap.get(key)
if (!deps) {
depsMap.set(key, (deps = new Set()))
}
// 把当前激活的副作用函数添加到依赖集合 deps 中
deps.add(activeEffect)
// deps 就是一个与当前副作用函数存在联系的依赖集合
// 将其添加到 activeEffect.deps 数组中
activeEffect.deps.push(deps)
}
// 为 trigger 函数增加第四个参数,newVal,即新值
function trigger(target, key, type, newVal) {
const depsMap = bucket.get(target)
if (!depsMap) return
// 取得与 key 相关联的副作用函数
const effects = depsMap.get(key)
const effectsToRun = new Set()
// 将与 key 相关联的副作用函数添加到 effectsToRun
effects && effects.forEach(effectFn => {
// 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
if (effectFn !== activeEffect) { // 新增
effectsToRun.add(effectFn)
}
})
// 当操作类型为 ADD 并且目标对象是数组时,应该取出并执行那些与 length 属性相关联的副作用函数
if (type === 'ADD' && Array.isArray(target)) {
// 取出与 length 相关联的副作用函数
const lengthEffects = depsMap.get('length')
// 将这些副作用函数添加到 effectsToRun 中,待执行
lengthEffects && lengthEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
// 如果操作目标是数组,并且修改了数组的 length 属性
if (Array.isArray(target) && key === 'length') {
// 对于索引大于或等于新的 length 值的元素,
// 需要把所有相关联的副作用函数取出并添加到 effectsToRun 中待执行
depsMap.forEach((effects, key) => {
if (key >= newVal) {
effects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
})
}
// 当操作类型为 ADD 或 DELETE 时,需要触发与 ITERATE_KEY 相关联的副作用函数重新执行
if (
type === 'ADD' ||
type === 'DELETE' ||
// 如果操作类型是 SET,并且目标对象是 Map 类型的数据,
// 也应该触发那些与 ITERATE_KEY 相关联的副作用函数重新执行
(
type === 'SET' &&
Object.prototype.toString.call(target) === '[object Map]'
)
) {
// 取得与 ITERATE_KEY 相关联的副作用函数
const iterateEffects = depsMap.get(ITERATE_KEY)
// 将与 ITERATE_KEY 相关联的副作用函数也添加到 effectsToRun
iterateEffects && iterateEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
effectsToRun.forEach(effectFn => {
if (effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn)
} else {
effectFn()
}
})
}
function cleanup(effectFn) {
// 遍历 effectFn.deps 数组
for (let i = 0; i < effectFn.deps.length; i++) {
// deps 是依赖集合
const deps = effectFn.deps[i]
// 将 effectFn 从依赖集合中移除
deps.delete(effectFn)
}
// 最后需要重置 effectFn.deps 数组
effectFn.deps.length = 0
}
// 用一个全局变量存储当前激活的 effect 函数
let activeEffect
// effect 栈
const effectStack = []
// effect 函数用于注册副作用函数
function effect(fn, options = {}) {
const effectFn = () => {
// 调用 cleanup 函数完成清除工作
cleanup(effectFn)
// 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect
activeEffect = effectFn
// 在调用副作用函数之前将当前副作用函数压入栈中
effectStack.push(effectFn)
fn()
// 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并把 activeEffect 还原为之前的值
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
// 将 options 挂载到 effectFn 上
effectFn.options = options
// activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
effectFn.deps = []
// 执行副作用函数
effectFn()
}
const p = reactive(new Map([
['key1', 'value1'],
['key2', 'value2']
]))
effect(() => {
for (const [key, value] of p) {
console.log(key, value)
}
})
for (const [key, value] of p.entries()) {
console.log(key, value)
}
代理 Set 和 Map 实现 values 与 keys 方法的响应性
实现集合数据类型的 values 与 keys 方法的响应性的思路也是:拦截原生的 values 与 keys 方法,调用 track 方法建立响应联系,然后返回实现了自定义的可迭代协议与迭代器协议的新对象。
js
const mutableInstrumentations = {
values: valuesIterationMethod,
keys: keysIterationMethod
}
function valuesIterationMethod() {
// 获取原始数据对象 target
const target = this.raw
// 通过 target.values 获取原始迭代器方法
const itr = target.values()
const wrap = (val) => typeof val === 'object' ? reactive(val) : val
track(target, ITERATE_KEY)
// 将其返回
return {
next() {
const { value, done } = itr.next()
return {
// value 是值,而非键值对,所以只需要包裹 value 即可
value: wrap(value),
done
}
},
[Symbol.iterator]() {
return this
}
}
}
const MAP_KEY_ITERATE_KEY = Symbol()
function keysIterationMethod() {
// 获取原始数据对象 target
const target = this.raw
// 获取原始迭代器方法
const itr = target.keys()
const wrap = (val) => typeof val === 'object' ? reactive(val) : val
// 调用 track 函数追踪依赖,在副作用函数与 MAP_KEY_ITERATE_KEY 之间建立响应联系
track(target, MAP_KEY_ITERATE_KEY)
// 将其返回
return {
next() {
const { value, done } = itr.next()
return {
value: wrap(value),
done
}
},
[Symbol.iterator]() {
return this
}
}
}
代理 Set 和 Map 实现 values 与 keys 方法的响应性的完整实现如下:
js
// 存储副作用函数的桶
const bucket = new WeakMap()
const ITERATE_KEY = Symbol()
const arrayInstrumentations = {}
;['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
const originMethod = Array.prototype[method]
arrayInstrumentations[method] = function(...args) {
// this 是代理对象,先在代理对象中查找,将结果存储到 res 中
let res = originMethod.apply(this, args)
if (res === false || res === -1) {
// res 为 false 说明没找到,通过 this.raw 拿到原始数组,再去其中查找,并更新 res 值
res = originMethod.apply(this.raw, args)
}
// 返回最终结果
return res
}
})
// 一个标记变量,代表是否进行追踪。默认值为 true,即允许追踪
let shouldTrack = true
// 重写数组的 push、pop、shift、unshift 以及 splice 方法
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
// 取得原始 push 方法
const originMethod = Array.prototype[method]
// 重写
arrayInstrumentations[method] = function(...args) {
// 在调用原始方法之前,禁止追踪
shouldTrack = false
// push 方法的默认行为
let res = originMethod.apply(this, args)
// 在调用原始方法之后,恢复原来的行为,即允许追踪
shouldTrack = true
return res
}
})
// 定义一个对象,将自定义的 add 方法定义到该对象下
const mutableInstrumentations = {
get(key) {
// 获取原始对象
const target = this.raw
// 判断读取的 key 是否存在
const had = target.has(key)
// 追踪依赖,建立响应联系
track(target, key)
// 如果存在,则返回结果。这里要注意的是,如果得到的结果 res 仍然是可代理的数据,
// 则要返回使用 reactive 包装后的响应式数据
if (had) {
const res = target.get(key)
return typeof res === 'object' ? reactive(res) : res
}
},
set(key, value) {
const target = this.raw
const had = target.has(key)
// 获取旧值
const oldValue = target.get(key)
// 获取原始数据,由于 value 本身可能已经是原始数据,所以此时 value.raw 不存在,则直接使用 value
const rawValue = value.raw || value
target.set(key, rawValue)
// 如果不存在,则说明是 ADD 类型的操作,意味着新增
if (!had) {
trigger(target, key, 'ADD')
} else if (oldValue !== value || (oldValue === oldValue && value === value)) {
// 如果不存在,并且值变了,则是 SET 类型的操作,意味着修改
trigger(target, key, 'SET')
}
},
add(key) {
// this 仍然指向的是代理对象,通过 raw 属性获取原始数据对象
const target = this.raw
// 先判断值是否已经存在
const hadKey = target.has(key)
const res = target.add(key)
// 只有在值不存在的情况下,才需要触发响应
if (!hadKey) {
trigger(target, key, 'ADD')
}
// 返回操作结果
return res
},
delete(key) {
const target = this.raw
const hadKey = target.has(key)
const res = target.delete(key)
// 当要删除的元素确实存在时,才触发响应
if (hadKey) {
trigger(target, key, 'DELETE')
}
return res
},
// 接收第二个参数
forEach(callback, thisArg) {
// wrap 函数用来把可代理的值转换为响应式数据
const wrap = (val) => typeof val === 'object' ? reactive(val) : val
// 取得原始数据对象
const target = this.raw
// 与 ITERATE_KEY 建立响应联系
track(target, ITERATE_KEY)
// 通过 target 调用原始 forEach 方法进行遍历
target.forEach((v, k) => {
// 通过 .call 调用 callback,并传递 thisArg
callback.call(thisArg, wrap(v), wrap(k), this)
})
},
// 共用 iterationMethod 方法
[Symbol.iterator]: iterationMethod,
entries: iterationMethod,
values: valuesIterationMethod,
keys: keysIterationMethod
}
function valuesIterationMethod() {
// 获取原始数据对象 target
const target = this.raw
// 通过 target.values 获取原始迭代器方法
const itr = target.values()
const wrap = (val) => typeof val === 'object' ? reactive(val) : val
track(target, ITERATE_KEY)
// 将其返回
return {
next() {
const { value, done } = itr.next()
return {
// value 是值,而非键值对,所以只需要包裹 value 即可
value: wrap(value),
done
}
},
[Symbol.iterator]() {
return this
}
}
}
const MAP_KEY_ITERATE_KEY = Symbol()
function keysIterationMethod() {
// 获取原始数据对象 target
const target = this.raw
// 获取原始迭代器方法
const itr = target.keys()
const wrap = (val) => typeof val === 'object' ? reactive(val) : val
// 调用 track 函数追踪依赖,在副作用函数与 MAP_KEY_ITERATE_KEY 之间建立响应联系
track(target, MAP_KEY_ITERATE_KEY)
// 将其返回
return {
next() {
const { value, done } = itr.next()
return {
value: wrap(value),
done
}
},
[Symbol.iterator]() {
return this
}
}
}
// 抽离为独立的函数,便于复用
function iterationMethod() {
// 获取原始数据对象 target
const target = this.raw
// 获取原始迭代器方法
const itr = target[Symbol.iterator]()
const wrap = (val) => typeof val === 'object' ? reactive(val) : val
// 调用 track 函数建立响应联系
track(target, ITERATE_KEY)
// 返回自定义的迭代器
return {
next() {
// 调用原始迭代器的 next 方法获取 value 和 done
const { value, done } = itr.next()
return {
// 如果 value 不是 undefined,则对其进行包裹
value: value ? [wrap(value[0]), wrap(value[1])] : value,
done
}
},
// 实现可迭代协议
[Symbol.iterator]() {
return this
}
}
}
// 增加第三个参数 isReadonly,代表是否只读,默认为 false,即非只读
function createReactive(obj, isShallow = false, isReadonly = false) {
return new Proxy(obj, {
deleteProperty(target, key) {
// 如果是只读的,则打印警告信息并返回
if (isReadonly) {
console.warn(`属性 ${key} 是只读的`)
return true
}
// 检查被操作的属性是否是对象自己的属性
const hadKey = Object.prototype.hasOwnProperty.call(target, key)
// 使用 Reflect.deleteProperty 完成属性的删除
const res = Reflect.deleteProperty(target, key)
if (res && hadKey) {
// 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新
trigger(target, key, 'DELETE')
}
return res
},
ownKeys(target) {
// 如果操作目标 target 是数组,则使用 length 属性作为 key 并建立响应联系
track(target, Array.isArray(target) ? 'length' : ITERATE_KEY)
return Reflect.ownKeys(target)
},
// 拦截读取操作
get(target, key, receiver) {
// 如果读取的是 raw 属性,则返回原始数据对象 target
if (key === 'raw') return target
if (key === 'size') {
// 调用 track 函数建立响应联系
track(target, ITERATE_KEY)
return Reflect.get(target, key, target)
}
// 返回定义在 mutableInstrumentations 对象下的方法
return mutableInstrumentations[key]
},
// 拦截设置操作
set(target, key, newVal, receiver) {
// 如果是只读的,则打印警告信息并返回
if (isReadonly) {
console.warn(`属性 ${key} 是只读的`)
return true
}
// 先获取旧值
const oldVal = target[key]
// 如果属性不存在,则说明是在添加新属性,否则是设置已有属性
const type = Array.isArray(target)
// 如果代理目标是数组,则检测被设置的索引值是否小于数组长度,
// 如果是,则视作 SET 操作,否则是 ADD 操作
? Number(key) < target.length ? 'SET' : 'ADD'
: Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
// 设置属性值
const res = Reflect.set(target, key, newVal, receiver)
// target === receiver.raw 说明 receiver 就是 target 的代理对象
if (target === receiver.raw) {
if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
// 增加第四个参数,即触发响应的新值
trigger(target, key, type, newVal)
}
}
return res
},
has(target, key) {
console.error('拦截 in 操作符')
track(target, key)
return Reflect.has(target, key)
}
})
}
// 定义一个 Map 实例,存储原始对象到代理对象的映射
const reactiveMap = new Map()
function reactive(obj) {
// 优先通过原始对象 obj 寻找之前创建的代理对象,如果找到了,直接返回已有的代理对象
const existionProxy = reactiveMap.get(obj)
if (existionProxy) return existionProxy
// 否则,创建新的代理对象
const proxy = createReactive(obj)
// 存储到 Map 中,从而避免重复创建
reactiveMap.set(obj, proxy)
return proxy
}
function shallowReactive(obj) {
return createReactive(obj, true)
}
function readonly(obj) {
return createReactive(obj, false, true /* 只读 */)
}
function shallowReadonly(obj) {
return createReactive(obj, true /* shallow */, true)
}
// 在 get 拦截函数内调用 track 函数追踪变化
function track(target, key) {
// 当禁止追踪时,直接返回
if (!activeEffect || !shouldTrack) return
let depsMap = bucket.get(target)
if (!depsMap) {
bucket.set(target, (depsMap = new Map()))
}
let deps = depsMap.get(key)
if (!deps) {
depsMap.set(key, (deps = new Set()))
}
// 把当前激活的副作用函数添加到依赖集合 deps 中
deps.add(activeEffect)
// deps 就是一个与当前副作用函数存在联系的依赖集合
// 将其添加到 activeEffect.deps 数组中
activeEffect.deps.push(deps)
}
// 为 trigger 函数增加第四个参数,newVal,即新值
function trigger(target, key, type, newVal) {
const depsMap = bucket.get(target)
if (!depsMap) return
// 取得与 key 相关联的副作用函数
const effects = depsMap.get(key)
const effectsToRun = new Set()
// 将与 key 相关联的副作用函数添加到 effectsToRun
effects && effects.forEach(effectFn => {
// 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
if (effectFn !== activeEffect) { // 新增
effectsToRun.add(effectFn)
}
})
// 当操作类型为 ADD 并且目标对象是数组时,应该取出并执行那些与 length 属性相关联的副作用函数
if (type === 'ADD' && Array.isArray(target)) {
// 取出与 length 相关联的副作用函数
const lengthEffects = depsMap.get('length')
// 将这些副作用函数添加到 effectsToRun 中,待执行
lengthEffects && lengthEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
// 如果操作目标是数组,并且修改了数组的 length 属性
if (Array.isArray(target) && key === 'length') {
// 对于索引大于或等于新的 length 值的元素,
// 需要把所有相关联的副作用函数取出并添加到 effectsToRun 中待执行
depsMap.forEach((effects, key) => {
if (key >= newVal) {
effects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
})
}
// 当操作类型为 ADD 或 DELETE 时,需要触发与 ITERATE_KEY 相关联的副作用函数重新执行
if (
type === 'ADD' ||
type === 'DELETE' ||
// 如果操作类型是 SET,并且目标对象是 Map 类型的数据,
// 也应该触发那些与 ITERATE_KEY 相关联的副作用函数重新执行
(
type === 'SET' &&
Object.prototype.toString.call(target) === '[object Map]'
)
) {
// 取得与 ITERATE_KEY 相关联的副作用函数
const iterateEffects = depsMap.get(ITERATE_KEY)
// 将与 ITERATE_KEY 相关联的副作用函数也添加到 effectsToRun
iterateEffects && iterateEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
if (
// 操作类型为 ADD 或 DELETE
(type === 'ADD' || type === 'DELETE') &&
// 并且是 Map 类型的数据
Object.prototype.toString.call(target) === '[object Map]'
) {
// 则取出那些与 MAP_KEY_ITERATE_KEY 相关联的副作用函数并执行
const iterateEffects = depsMap.get(MAP_KEY_ITERATE_KEY)
iterateEffects && iterateEffects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
}
effectsToRun.forEach(effectFn => {
if (effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn)
} else {
effectFn()
}
})
}
function cleanup(effectFn) {
// 遍历 effectFn.deps 数组
for (let i = 0; i < effectFn.deps.length; i++) {
// deps 是依赖集合
const deps = effectFn.deps[i]
// 将 effectFn 从依赖集合中移除
deps.delete(effectFn)
}
// 最后需要重置 effectFn.deps 数组
effectFn.deps.length = 0
}
// 用一个全局变量存储当前激活的 effect 函数
let activeEffect
// effect 栈
const effectStack = []
// effect 函数用于注册副作用函数
function effect(fn, options = {}) {
const effectFn = () => {
// 调用 cleanup 函数完成清除工作
cleanup(effectFn)
// 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect
activeEffect = effectFn
// 在调用副作用函数之前将当前副作用函数压入栈中
effectStack.push(effectFn)
fn()
// 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并把 activeEffect 还原为之前的值
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
// 将 options 挂载到 effectFn 上
effectFn.options = options
// activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合
effectFn.deps = []
// 执行副作用函数
effectFn()
}
const p = reactive(new Map([
['key1', 'value1'],
['key2', 'value2']
]))
effect(() => {
for (const value of p.keys()) {
console.log(value)
}
})
p.set('key2', 'value3') // 不会触发响应
p.set('key3', 'value3') // 能够触发响应