blog-frontend
blog-frontend copied to clipboard
@vue/reactivity源码学习
1.前置知识
1.1 Reflect的意义
JavaScript 语言内置的 Reflect 对象上的函数是为 Proxy 准备的,Proxy 的 handler 的各种 trap 分别对应 Reflect 上的同名方法。 在 Proxy 的 handler 中使用 Reflect 上对应的方法来实现默认行为。
function logAll(o) {
const handler = {}
for (const op of Object.getOwnPropertyNames(Reflect)) {
handler[op] = (...args) => {
try {
return Reflect[op](...args)
} finally {
console.log(op)
}
}
}
return new Proxy(o, handler)
}
1.2 WeakMap
特点:
- WeakMap的key只能是Object类型
- WeakMap的key持有的是"弱引用",这意味在没有其他引用存在时,垃圾回收能正确进行
- WeakMap的key是不可枚举的
1.3 如何调试
- clone
vue-next
- 在项目根目录运行
yarn dev reactivity
- 创建一个html文件, 然后去source面板操作
<script src="reactivity.global.js"></script>
<script>
const { reactive, computed } = VueReactivity
const r = reactive({ number: 1 })
debugger
const c = computed(() => r.number)
console.log(c.value)
</script>
阅读源码最简单有效的办法就是调试了
2. 翻译翻译reactive({ number: 1 })
结论:reactive({ number: 1 })
等价于new Proxy({ number: 1 }, mutableHandlers)
function reactive(target) {
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers);
}
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
const reactiveFlag = isReadonly ? "__v_readonly" : "__v_reactive";
const observed = new Proxy(target, baseHandlers);
def(target, reactiveFlag, observed);
return observed;
}
结论:mutableHandlers
包含get/set/deleteProperty/has/onwKeys
五个捕捉器,他们除了完成默认行为之外,还会调用trigger/track
方法
function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
track(target, "get" /* GET */, key);
// Proxy只会代理对象的第一层, 如果Reflect.get的返回值是对象,则再通过reactive方法做代理,实现更深层级的响应式
if (isObject(res)) {
return reactive(res)
}
return res;
}
function set(target, key, value, receiver) {
const oldValue = target[key];
value = toRaw(value);
const hadKey = hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
if (!hadKey) {
trigger(target, "add" /* ADD */, key, value);
}
else if (hasChanged(value, oldValue)) {
trigger(target, "set" /* SET */, key, value, oldValue);
}
return result;
}
function deleteProperty(target, key) {
const hadKey = hasOwn(target, key);
const oldValue = target[key];
const result = Reflect.deleteProperty(target, key);
if (result && hadKey) {
trigger(target, "delete" /* DELETE */, key, undefined, oldValue);
}
return result;
}
function has(target, key) {
const result = Reflect.has(target, key);
track(target, "has" /* HAS */, key);
return result;
}
function ownKeys(target) {
track(target, "iterate" /* ITERATE */, ITERATE_KEY);
return Reflect.ownKeys(target);
}
const mutableHandlers = {
get,
set,
deleteProperty,
has,
ownKeys
}
track/trigger
的实际作用不能孤立来看,首先需要了解computed
的实现。
3. 翻译翻译Computed
用过computed
的人都模糊的知道:响应式数据更新了,计算属性就会更新,这里有两点需要搞明白:
- 计算属性什么时候更新:是立刻更新,还是触发
get
的时候更新? - 如何知道函数依赖了那些响应式数据的属性?也就是收集依赖
3.1 计算属性什么时候更新:是立刻更新,还是触发get
的时候更新?
computed
源码如下,这里注意constructor
和get
// 主要处理入参,赋值给getter和setter
function computed(getterOrOptions) {
let getter;
let setter;
if (isFunction(getterOrOptions)) {
getter = getterOrOptions;
setter = NOOP;
}
else {
getter = getterOrOptions.get;
setter = getterOrOptions.set;
}
return new ComputedRefImpl(getter, setter, isFunction(getterOrOptions) || !getterOrOptions.set);
}
class ComputedRefImpl {
constructor(getter, _setter, isReadonly) {
this._setter = _setter;
// 众所周知,计算属性是有"缓存"的,依赖值没有改变时,当然只计算一次,_dirty可以理解为"是否需要重新计算"
this._dirty = true;
this.__v_isRef = true;
// 可以简单推测出effect的作用:getter函数中依赖的响应式数据的属性更新了,就将_dirty设置为"需要重新计算"的状态
this.effect = effect(getter, {
lazy: true,
scheduler: () => {
if (!this._dirty) {
this._dirty = true;
trigger(toRaw(this), "set" /* SET */, 'value');
}
}
});
this["__v_isReadonly" /* IS_READONLY */] = isReadonly;
}
get value() {
// 需要重新计算,从effect中拿到值,否则返回缓存
if (this._dirty) {
this._value = this.effect();
this._dirty = false;
}
track(toRaw(this), "get" /* GET */, 'value');
return this._value;
}
// 单纯的调用一下提供的setter,不重要
set value(newValue) {
this._setter(newValue);
}
}
结论:计算属性不是在依赖的响应式数据变化后立即更新的,而是get value
时按需更新
3.2 如何收集依赖?
承接上文,在脑海中建立一个computed => effect
的映射,effect
接收的第一个函数,也就是computed
中的getter
function effect(fn, options = EMPTY_OBJ) {
// 避免二次包装
if (isEffect(fn)) {
fn = fn.raw;
}
const effect = createReactiveEffect(fn, options);
// 是否需要立即执行,从这里可见,computedRef的构造函数中初始化`effect`不会立即执行
if (!options.lazy) {
effect();
}
return effect;
}
const effectStack = [];
let activeEffect;
let uid = 0;
function createReactiveEffect(fn, options) {
const effect = function reactiveEffect() {
// 避免重复入栈
if (!effectStack.includes(effect)) {
// 收集依赖前,先清理一次之前effect的依赖
cleanup(effect);
try {
// 当前effect入栈
effectStack.push(effect);
// 维护activeEffect
activeEffect = effect;
// computed中的getter被执行,触发了响应式数据的getter,从而执行了`track`方法,收集到依赖
return fn();
}
finally {
// 当前effect出栈
effectStack.pop();
// 维护activeEffect
activeEffect = effectStack[effectStack.length - 1];
}
}
};
effect.id = uid++;
effect._isEffect = true;
effect.active = true;
effect.raw = fn;
effect.deps = [];
effect.options = options;
return effect;
}
结论:在执行effect函数时,最终会调用原始函数fn
,此时会触发响应式数据的getter
, 进而调用track
函数,完成依赖收集
4. 依赖表
依赖收集的数据结构targetMap
整体是一个WeakMap
,key
为响应式对象,value
为Map<被依赖的属性,Set<ReactiveEffect>>
// The main WeakMap that stores {target -> key -> dep} connections.
// Conceptually, it's easier to think of a dependency as a Dep class
// which maintains a Set of subscribers, but we simply store them as
// raw Sets to reduce memory overhead.
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()
4.1 track函数
维护依赖表
function track(target, type, key) {
if (!shouldTrack || activeEffect === undefined) {
return;
}
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
}
4.2 trigger函数
从依赖表中取出对应的effects
,执行
function trigger(target, type, key, newValue, oldValue, oldTarget) {
// 这一堆代码就是找effects罢了
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
const effects = new Set();
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect) {
effects.add(effect);
}
});
}
};
if (key !== void 0) {
add(depsMap.get(key));
}
// 如果提供了调度函数,就执行调度函数
const run = (effect) => {
if (effect.options.scheduler) {
effect.options.scheduler(effect);
}
else {
effect();
}
};
// 全都执行一遍
effects.forEach(run);
}
5. 整体流程回顾
-
const r = reactive({ number: 1 })
- 创建了一个响应式数据:实际等于
new Proxy(target, mutableHandlers)
,mutableHandlers
包含get/set/ownKeys/has/deleteProperty
捕获器,他们会调用track/trigger
方法
- 创建了一个响应式数据:实际等于
-
const c = computed(() => r.number)
- 创建了一个计算属性:实际上初始化了一个
effect(() => r.number, { lazy: true, scheduler: 略 })
函数, 但是并没有执行,此时c.value
实际为undefined
- 创建了一个计算属性:实际上初始化了一个
-
console.log(c.value)
- 首次获取计算属性的值,让
effect(() => r.number, { lazy: true, scheduler: 略 })
得到执行,并在最终执行() => r.number
时,触发了响应式数据c
的getter
- 收集到了依赖:
track(r, 'get', 'number')
- 此时依赖表为:
reactiveObj reactiveAttribute effects r number effect(() => r.number, { lazy: true, scheduler: 略 })
- 首次获取计算属性的值,让
-
r.number = 10
- 触发了响应式对象r的
setter
- 触发更新:
trigger(r, 'set', 'number', 10, 1)
- 从依赖表中取出对应reactiveObj, reactiveAttribute下的
effects
执行 - 在这个例子中执行
effect.options.scheduler
函数,将computedRef
的_dirty
属性设置为"需要更新的状态"
- 触发了响应式对象r的
-
console.log(c.value)
- 第二次获取计算属性的值,由于
_dirty
为"需要更新的状态",所以再次执行effect
函数获取新值
- 第二次获取计算属性的值,由于
排面