这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天
reactive板块-简易框架里的代码分析
树形结构
上文提到没有建立联系
原因:当读取属性时,无论读取 的是哪一个属性,其实都一样,都会把副作用函数收集到“桶”里;当 设置属性时,无论设置的是哪一个属性,也都会把“桶”里的副作用函 数取出并执行。
解决办法:依靠一个保存有依赖与更新对应关系的WeakMap数据结构--书中给了这样的树形结构
所以使用 WeakMap 代替 Set 作为桶的数据结构:
WeakMap
- targetMap: WeakMap 类型,用来记录目标对象和depsMap的关系
- depsMap: Map 类型,用来记录目标对象属性和dep的关系
- dep: Set 类型,用来记录属性对应的更新函数
let targetMap = new WeakMap()
function track(target, key) {
// 判断activeEffect是否存在
if (!activeEffect) {
return;
}
// depsMap存储对象和effect的对应关系
let depsMap = targetMap.get(target)
// 如果不存在则创建一个map存储到targetMap中
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// 根据属性查找对应的dep对象
let dep = depsMap.get(key)
// dep是一个集合,用于存储属性所对应的effect函数
if (!dep) {
// 如果不存在,则创建一个新的集合添加到depsMap中
depsMap.set(key, (dep = new Set()))
}
dep.add(activeEffect)
}
优点:
-
WeakMap 对 key 是弱引用,不影响垃圾回收器的工 作。一旦 key 被垃圾回收器回收,那么对应的键和 值就访问不到了。所以 WeakMap 经常用于存储那些只有当 key 所引 用的对象存在时(没有被回收)才有价值的信息。
-
如果使用 Map 来代替 WeakMap, 那么即使用户侧的代码对 target 没有任何引用,这个 target 也不 会被回收,最终可能导致内存溢出。
effect (有点缺漏)
let activeEffect = null;
function effect(callback) {
activeEffect = callback;
// 访问响应式对象属性,收集依赖
callback();
// 依赖收集结束要置null
activeEffect = null;
}
trigger
function trigger(target, key) {
const depsMap = targetMap.get(target)
// 如果没有找到直接返回
if (!depsMap) {
return;
}
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => {
effect()
})
}
}
嵌套对象
// 判断是否是一个对象
const isObject = val => val !== null && typeof val === 'object'
// 如果是对象则调用reactive
const convert = target => isObject(target) ? reactive(target) : target
// 判断对象是否存在key属性
const haOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target, key) => haOwnProperty.call(target, key)
function reactive(target) {
if (!isObject(target)) {
// 如果不是对象直接返回
return target
}
const handler = {
get(target, key, receiver) {
// 收集依赖
track(target, key)
const result = Reflect.get(target, key, receiver)
console.log(result, "get")
// 如果属性是对象则需要递归处理
return convert(result)
},
set(target, key, value, receiver) {
const oldValue = Reflect.get(target, key, receiver)
let result = true;
// 需要判断当前传入的新值和oldValue是否相等,如果不相等再去覆盖旧值,并且触发更新
if (oldValue !== value) {
result = Reflect.set(target, key, value, receiver)
// 触发更新...
trigger(target, key)
}
// set方法需要返回布尔值
console.log(result, "set")
return result;
},
deleteProperty(target, key) {
// 首先要判断当前target中是否有自己的key属性
// 如果存在key属性,并且删除要触发更新
const hasKey = hasOwn(target, key)
const result = Reflect.deleteProperty(target, key)
if (hasKey && result) {
// 触发更新...
trigger(target, key)
}
return result;
}
}
return new Proxy(target, handler)
}
补充:
在compile模块里的v-model命令,当涉及到input框里修改值的时候会多次改变,所以执行的时候加入了防抖功能
model(node, vm, expr) {
node.value = this.getVMValue(vm, expr)
// 实现双向的数据绑定,给NODE注册INPUT事件,当前元素的value发生改变,便修改node数据
node.addEventListener("input", MyDebounce(function () {
vm.$data[expr] = this.value
}))
effect(() => {
node.value = this.getVMValue(vm, expr)
})
},