整体结论
-
reactive做数据代理和负责依赖收集的触发
-
effect 做数据副作用的触发函数,函数内部自运行effect()触发reactive的get
-
track get的触发做effectFn的快照收集,结构用weakMap保存(下面详细介绍),依赖收集已经做好
-
trigger 当原始值删除或者改变触发reactive中set和deleteProperty执行trigger,他把weakMap中的effectFn找出来然后重新执行一遍
tips:建议与下面简版源码一起食用
weakMap中的数据结构
单独拧出来说,搞懂这个基本搞懂50%~
const obj = reactive({ a: 'a', b: { c: 1 } })//测试数据,生成的weakMap如下

三层结构:weakMap ==>Map ==>Set
-
weakMap:存储对象和key的关系
- key:target对象,对应的是effect中引用的层级深度,也就是说{ a: 'a', b: { c: 1 } }一个 { c: 1 }两个 为什么是这样设计的呢?因为proxy的回调target就是原始对象,也在做深度响应的时候,通过对象去找也只会触发一次trigger()
- vaue:Map
-
Map:存储key和effectFn的关系
- key:对应的每个层级的key a/b c
- value:Set
-
Set:存储对应的effect函数
- value:effect传进来的函数做存储
下面直接贴代码~
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="aDom"></div>
<div id="bDom"></div>
<script>
//数据响应式
function reactive(obj) {
if (!isObject(obj)) {
return obj
}
return new Proxy(obj, {
get(target, key) {
//Reflect容错率不错,错误会正确的处理
const res = Reflect.get(target, key)
console.log('get', res)
track(target, key)
return isObject(res) ? reactive(res) : res
},
set(target, key, value) {
const res = Reflect.set(target, key, value)
console.log('set', key)
trigger(target, key)
return res
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key)
console.log('del', key)
trigger(target, key)
return res
}
})
}
function isObject(obj) {
return typeof obj === 'object'
}
// 保存函数
const effectStack = []
//保存映射关系的数据结构
const targetMap = new WeakMap()
//触发依赖收集
function effect(fn) {
//对照源码简化的高阶函数,可以从简,但是为了原滋原味
const e = createReactiveEffect(fn)
//自运行
e()
return e
}
function createReactiveEffect(fn) {
//错误处理
//effectStack 进栈
//执行完依赖收集后出栈保证effectStack最后值的获取
const effectFn = function () {
try {
effectStack.push(effectFn)
fn()
} finally {
effectStack.pop()
}
}
return effectFn
}
//跟踪函数:负责依赖收集
function track(target, key) {
//1. 获取effectFn
const effect = effectStack[effectStack.length - 1]
if (effect) {
//用过target获取对应的map 每一层级的数据对应一个targetMap target:Map
let depMap = targetMap.get(target)
//首次进入depMap为空,需要创建
if (!depMap) {
depMap = new Map()
targetMap.set(target, depMap)
}
// 2. 通过key获取依赖集合set
let deps = depMap.get(key)
if (!deps) {
deps = new Set()
depMap.set(key, deps)
}
// 3. 放入effect
deps.add(effect)
//层级关系是 obj:{key:effect} 三层套娃 WeakMap ==> Map ==>Set
}
}
//触发函数:track()相反操作,拿出映射关系,执行所有的cbs
function trigger(target, key) {
const depMap = targetMap.get(target)
if (!depMap) return
const deps = depMap.get(key)
if (deps) {
deps.forEach(dep => dep())
}
}
const obj = reactive({ a: 'a', b: { c: 1 } })
//源码中 这里做虚拟dom的path,这里简单的用textContent改变页面上的值做响应式
effect(() => {
aDom.textContent = obj.a
console.log('effect1', obj.a)
})
//深层次的副作用
effect(() => {
bDom.textContent = obj.b.c
console.log('effect2', obj.b.c)
})
setTimeout(() => {
obj.a = '777'
//当改变深度子节点 只会触发一次trigger 因为weakMap是以obj的target(递归中就是每一层的对象)存储的
obj.b.c = "666"
}, 2000)
</script>
</body>
</html>
都看到这里了~三连我就不要了,点个赞就行~哈哈
网友评论