前言
首先感谢__mxin同学的简易版本,没有这个简化版本,我大概率也没办法沉下心来将代码读下去,再次表示感谢,通读下来简化之后的逻辑清晰,只需要对几个JavaScript原生API进行了解,走完代码流程,便了解了核心流程
代码地址:传送门
前置知识
Proxy
Reflect
WeakMap
核心流程图
尝试绘制了一遍代码流程图,主要流程就是初始化时候对reactive,computed,effect的依赖收集,以及在触发set事件的时候,对收集到的依赖的触发
reactive
1 2 3 4 5 6 7 8 9
| const object = { r: 0, g: 0, b: 0, o: { a: 1, }, } const proxy = reactive(object)
|
reactive是一个赋予对象响应式特征的方法,传入的数据会被proxy代理,变量一旦被代理,就将会被加入reactiveMap,以后都会触发reactiveMap内的proxy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
function reactive(object) {
if (reactiveMap.has(object)) return reactiveMap.get(object) const proxy = new Proxy(object, { get(target, key) { console.log('get方法', target, key) track(target, key) return typeof target[key] === 'object' ? reactive(target[key]) : Reflect.get(...arguments) }, set(target, key) { console.log('设置的值', ...arguments) Reflect.set(...arguments) }, })
reactiveMap.set(object, proxy) return proxy }
|
完成代理的数据
effect
effect会在依赖的经过reactive处理后的对象发生变化的时候,自动执行一次回调函数,通常称它为副作用函数
effect的实现是Reactivity最核心的部分,也是比较难理解的部分,依赖WeakMap进行实现,如果不了解WeakMap,务必先去看一下文档
1 2 3 4 5 6 7
| const computedObj = computed(() => { return proxy.r * 2 })
effect(() => { console.log(`proxy.o.a: ${proxy.o.a}`) })
|
初始化的过程中触发effect,将函数fn放入effectStack,同时执行effect中的函数,一旦执行,必定会触发经过reactive代理的get函数,进行数据获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| const effectStack = []
function effect(fn) { try { effectStack.push(fn) return fn() } finally { effectStack.pop() } }
get(target, key) { track(target, key) }
function track(target, key) { let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { targetMap.get(target).set(key, (dep = new Set())) } const activeEffect = effectStack[effectStack.length - 1] activeEffect && dep.add(activeEffect) }
|
初始化完成后,effect全部完成处理,我们可以看一下targetMap的数据
我们可以看到,变量a与effect中的函数关联在了一起,经过track处理后,effect内部用到的变量都与effect建立了某种关联,至此我们就完成了依赖收集
computed
Reactivity计算属性的实现是依赖effect进行实现,仅仅是增加了一个value函数进行包裹
1 2 3 4 5 6 7 8 9 10
|
function computed(fn) { return { get value() { return effect(fn) }, } }
|
变量发生变化
数据发生变化的时候,例如我们将proxy.o.a=1,他是如何完成响应式,以及effect的触发的呢?
首先一定是触发proxy的set函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| set(target, key) Reflect.set(...arguments) trigger(target, key) },
function trigger(target, key) { const depMap = targetMap.get(target) if (depMap) { const effects = depMap.get(key) effects && effects.forEach((run) => { run() }) } }
|
至此完成数据的响应式,effect的函数触发完成
关键概念
reactive 创建响应式对象
effect 副作用函数,存储匿名函数,同时调用自身收集依赖,最后弹出匿名函数
computed 计算属性,其原理是对effect的包装
track 收集依赖,绑定变量与使用该变量的effect
trigger 触发依赖,根据变量触发对应的effect
总结
这个文章是一个代码记录贴,希望大家看到可以静下心来看看__mxin同学的文章,或者传送门代码,了解了基础的原理后再去看@vue/Reactivity的代码,将会事半功倍;
日积月累,将知识变成你的财富吧