Vue的响应式系统让人着迷,在Vue2时期使用的是Object.defineProperty,而到了Vue3则变成了Proxy。既然我们想要了解Vue3的响应式原理,就要先明白什么是响应式。
什么是响应式
首先要明白,响应式是一个过程,它有两个参与方:触发者:数据;响应者:引用的数据函数。
当数据发生改变时,引用数据的函数就会自动重新执行。例如:在进行视图渲染时使用了数据,数据发生了改变,视图也会自动更新,这就完成了一个响应的过程。
这张图详细的展示了Vue3中响应式是如何工作的。
Proxy和Reflect
在了解Vue3响应式原理之前,还需要了解Proxy和Reflect
Proxy
顾名思义,Proxy主要是为对象创建一个代理,从而实现对对象基本操作的拦截和自定义。简单来说就是在目标对象前设置一层“拦截”,外界对该对象的访问,都必须先通过这层拦截。因此提供了一种机制,可以对外界的访问进行过滤和改写。基本语法:
let proxy = new Proxy (target,handler);
target
:需要拦截的对象。handler
:也是一个对象,来定制拦截行为。举个栗子:
const obj = {
name: 'John',
age: 16
}
const objProxy = new Proxy(obj,{})
objProxy.age = 20
console.log('obj.age',obj.age);
console.log('objProxy.age',objProxy.age);
console.log('obj与objProxy是否相等',obj === objProxy);
// 输出
[Log] obj.age – 20
[Log] objProxy.age – 20
[Log] obj与objProxy是否相等 – false
这里的 objProxy 的 handler 为空,则直接指向代理对象,并且代理对象和数据源对象不完全相等。如果想要更加灵活的拦截对象的操作,就需要在 handler 中添加相应的属性。比如这样:
const obj = {
name: 'John',
age: 16
}
const handler = {
get(target, key, receiver) {
console.log(`获取对象属性${key}值`)
return target[key]
},
set(target, key, value, receiver) {
console.log(`设置对象属性${key}值`)
target[key] = value
},
deleteProperty(target, key) {
console.log(`删除对象属性${key}值`)
return delete target[key]
},
}
const proxy = new Proxy(obj, handler)
console.log(proxy.age)
proxy.age = 20
console.log(delete proxy.age)
// 输出
[Log] 获取对象属性age值 (example01.html, line 22)
[Log] 16 (example01.html, line 36)
[Log] 设置对象属性age值 (example01.html, line 26)
[Log] 删除对象属性age值 (example01.html, line 30)
[Log] true (example01.html, line 38)
在上面的代码中,我们在捕获器中定义了set()、get()、deleteProperty()属性,通过对 proxy 的操作实现了对 obj 的操作拦截。其中出现了多种属性,我们来一一看下:
- target:是目标对象,该对象会作为第一个参数传递给 new Proxy。
- key:目标属性名称。
- value:目标属性的值。
- receiver:指向当前操作 正确的上下文。如果目标属性是一个 getter 访问器属性。那 receiver 就是本次读取属性所指向的 this 对象。通常,receiver 是 Proxy 对象本身,但如果我们从 proxy 继承,那么 receiver 指的是 proxy 继承的对象。
- has():拦截 in操作符。
- ownKeys():拦截
Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
。 construct():拦截new
操作等。
Reflect
反射,就是将代理的内容反射出去。Reflect
和 proxy
一样,都是 ES6 为了操作对象而提供的新 API
。它提供了拦截 JavaScript 操作的方法。这些方法和 Proxy handlers 提供的方法是一一对应的,也就是说,只要是 Proxy 对象上的方法,就能在 Reflect 对象上找到相应的方法。而且 Reflect 不是函数对象,也就意味着不能实例化。所有的属性和方法都是静态的。
const obj = {
name: 'John',
age: 16
}
const handler = {
get(target, key, receiver) {
console.log(`获取对象属性${key}值`)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
console.log(`设置对象属性${key}值`)
return Reflect.set(target, key, value, receiver)
},
deleteProperty(target, key) {
console.log(`删除对象属性${key}值`)
return Reflect.deleteProperty(target, key)
},
}
const proxy = new Proxy(obj, handler)
console.log(proxy.age)
proxy.age = 20
console.log(delete proxy.age)
这个例子中 Reflect.get()
代替 target[key]
;Reflect.set()
代替 target[key]=value
; Reflect.deleteProperty()
代替 delete target[key]
。
Reactive 和 Effect
在了解了 Proxy 和 Reflect 之后,再来看下Vue3是如何通过 proxy 来实现响应式的。
Reactive
Reactive是Vue实现响应式的方法之一,根据官网的介绍,有以下特点:
- 接受一个普通对象,返回一个响应式的代理对象。
- 响应式对象是深层的,会影响对象内部所有嵌套的属性。
- 自动对ref对象解包。
- 对于数组、对象、Map、Set等原生类型的元素,如果是ref对象不会自动解包。
- 返回的对象会通过Proxy进行包装,所以返回的对象不是原始对象。
Reactive 的源码在packages/reactivity/src/reactive.ts
中 ,但在这里我们只看编译成js
的代码。
function reactive(target) {
// 如果对只读的代理对象进行再次代理,那么应该返回原始的只读代理对象
if (isReadonly(target)) {
return target;
}
// 通过 createReactiveObject 方法创建响应式对象
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
Reactive 的源码比较简单,就是调用了 createReactiveObject
方法,这是个工厂方法,用来创建响应式对象的,再来看下这个方法的源码:
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
// 如果 target 不是对象,那么直接返回 target
if (!isObject(target)) {
{
console.warn(`value cannot be made reactive: ${String(target)}`);
}
return target;
}
// 如果 target 已经是一个代理对象了,那么直接返回 target
// 异常:如果对一个响应式对象调用 readonly() 方法
if (target["__v_raw" /* ReactiveFlags.RAW */] &&
!(isReadonly && target["__v_isReactive" /* ReactiveFlags.IS_REACTIVE */])) {
return target;
}
// 如果 target 已经有对应的代理对象了,那么直接返回代理对象
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
// 对于不能被观察的类型,直接返回 target
const targetType = getTargetType(target);
if (targetType === 0 /* TargetType.INVALID */) {
return target;
}
// 创建一个响应式对象
const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers);
// 将 target 和 proxy 保存到 proxyMap 中
proxyMap.set(target, proxy);
// 返回 proxy
return proxy;
}
这个方法的代码也很简单,最开始是对需要代理的 target
进行判断,判断标准都是 target
不是对象和target
已经是一个代理对象。
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
// 对于不能被观察的类型,直接返回 target
const targetType = getTargetType(target);
if (targetType === 0 /* TargetType.INVALID */) {
return target;
}
// 创建一个响应式对象
const proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers);
// 将 target 和 proxy 保存到 proxyMap 中
proxyMap.set(target, proxy);
// 返回 proxy
return proxy;
}
这几行代码是核心代码,这里涉及一个targetType
的判断,那这个targetType
又是什么?让我们来看下getTargetType
方法的源码:
// 获取原始数据类型
const toRawType = (value) => {
// extract "RawType" from strings like "[object RawType]"
return toTypeString(value).slice(8, -1);
};
// 获取数据类型
function targetTypeMap(rawType) {
switch (rawType) {
case 'Object':
case 'Array':
return 1 /* TargetType.COMMON */;
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return 2 /* TargetType.COLLECTION */;
default:
return 0 /* TargetType.INVALID */;
}
}
// 获取 target 的类型
function getTargetType(value) {
return value["__v_skip" /* ReactiveFlags.SKIP */] || !Object.isExtensible(value)
? 0 /* TargetType.INVALID */
: targetTypeMap(toRawType(value));
}
这里是枚举出原始数据类型,它的返回值是:
const enum TargetType {
// 无效的数据类型,对应的值是 0,表示 Vue 不会对这种类型的数据进行响应式处理
INVALID = 0,
// 普通的数据类型,对应的值是 1,表示 Vue 会对这种类型的数据进行响应式处理
COMMON = 1,
// 集合类型,对应的值是 2,表示 Vue 会对这种类型的数据进行响应式处理
COLLECTION = 2
}
export const enum ReactiveFlags {
// 用于标识一个对象是否不可被转为代理对象,对应的值是 __v_skip
SKIP = '__v_skip',
// 用于标识一个对象是否是响应式的代理,对应的值是 __v_isReactive
IS_REACTIVE = '__v_isReactive',
// 用于标识一个对象是否是只读的代理,对应的值是 __v_isReadonly
IS_READONLY = '__v_isReadonly',
// 用于标识一个对象是否是浅层代理,对应的值是 __v_isShallow
IS_SHALLOW = '__v_isShallow',
// 用于保存原始对象的 key,对应的值是 __v_raw
RAW = '__v_raw'
}
这里将枚举值和含义都列出来了,把注释和源码结合起来理解,会更容易理解源码。
collectionHandlers和baseHandlers
createReactiveObject
方法是返回一个代理对象。而关键点是这个代理对象的handler
,而这个handler
是由collectionHandlers
和baseHandlers
这两个对象组成。
在源码中通过targetType
来判断使用哪种handler
,当targetType
的值为2时使用collectionHandlers
,否则使用baseHandlers
。
其实targetType
只有三个值,走到代理的也只有两种情况:
targetType
为1时,target
是一个普通对象或数组,这是用baseHandlers
。targetType
为2时,target
是一个集合类型,这时用collectionHandlers
。
这两个handler
都是由外部传入的,也就是createReactiveObject
方法的第三和第四参数,而传入这两个参数的地方就是reactive
方法:
function reactive(target) {
// ...
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
可以看出,mutableHandlers
和mutableCollectionHandlers
分别对应baseHandlers
和collectionHandlers
。
baseHandlers
mutableHandlers
是baseHandlers
的一个export。来看以下代码:
const mutableHandlers = {
get: get$1,
set: set$1,
deleteProperty,
has: has$1,
ownKeys
};
这里设置了几个拦截器,我们来分别介绍下作用:
- get:拦截getter操作,如obj.name。
- set:拦截setter操作,如obj.name="张三"。
- deleteProperty:拦截delete操作。
- has:拦截in操作。
- ownKeys:拦截
Object.getOwnPropertyNames
、Object.getOwnPropertySymbols
、Object.keys
等操作。 接下来详细介绍每个拦截器。
get
function get(target, key, receiver) {
// 如果访问的是 __v_isReactive 属性,那么返回 isReadonly 的取反值
if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */) {
return !isReadonly;
}
// 如果访问的是 __v_isReadonly 属性,那么返回 isReadonly 的值
else if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */) {
return isReadonly;
}
// 如果访问的是 __v_isShallow 属性,那么返回 shallow 的值
else if (key === "__v_isShallow" /* ReactiveFlags.IS_SHALLOW */) {
return shallow;
}
// 如果访问的是 __v_raw 属性,并且有一堆条件满足,那么返回 target
else if (key === "__v_raw" /* ReactiveFlags.RAW */ &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap).get(target)) {
return target;
}
// ...
};
这段代码是来处理一些特殊的属性,这都是Vue内定义好的,也就是上文提到的枚举值。来判断reactive、readonly、shallow等。
接下来的这些代码才是至关重要的。
首先是对数组的访问处理:
function get(target, key, receiver) {
// ...
// target 是否是数组
const targetIsArray = isArray(target);
// 如果不是只读的
if (!isReadonly) {
// 如果是数组,并且访问的是数组的一些方法,那么返回对应的方法
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver);
}
// 如果访问的是 hasOwnProperty 方法,那么返回 hasOwnProperty 方法
if (key === 'hasOwnProperty') {
return hasOwnProperty;
}
}
// ...
};
这段代码是一些处理数组的方法,比如push、pop等。当调用这些方法时,就会执行这段代码并返回对应方法。例如:
const arr = reactive([1, 2, 3]);
arr.push(4);
在获取返回值:
function get(target, key, receiver) {
// ...
// 获取 target 的 key 属性值
const res = Reflect.get(target, key, receiver);
// ...
};
到这一步,就需要target的key属性值,这里使用了Reflect.get。这是ES6新增的访问对象属性的方法。和target[key]
等价,但Reflect.get可以传入receiver。
对特殊属性不进行依赖收集:
function get(target, key, receiver) {
// ...
// 如果是内置的 Symbol,或者是不可追踪的 key,那么直接返回 res
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res;
}
// ...
};
这里是过滤掉一些内置属性,因为它们不会改变就不需要收集。
依赖收集:
function get(target, key, receiver) {
// ...
// 如果不是只读的,那么进行依赖收集
if (!isReadonly) {
track(target, "get" /* TrackOpTypes.GET */, key);
}
// ...
};
这里调用了track方法来收集依赖。
浅的不进行递归:
function get(target, key, receiver) {
// ...
// 如果是浅的,那么直接返回 res
if (shallow) {
return res;
}
// ...
};
这一步是为了处理shallow
的情况,如果是shallow
,就不进行递归代理,直接返回res。
对返回值解包:
function get(target, key, receiver) {
// ...
// 如果 res 是 ref,对返回的值进行解包
if (isRef(res)) {
// 对于数组和整数类型的 key,不进行解包
return targetIsArray && isIntegerKey(key) ? res : res.value;
}
// ...
};
这里是处理ref
的情况,如果res是ref
,那就对res进行解包。这里还有一个判断,如果是数组,且key是整数类型,就不进行解包。
代理返回值:
function get(target, key, receiver) {
// ...
// 如果 res 是对象,那么对返回的值进行代理
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res);
}
// ...
};
这里添加了一个判断,如果是readonly
,就用readonly
方法,否则就使用reactive
方法来代理。
以上就是对get
方法的拆分讲解,接下来再看下set
方法。
set
set
方法比get
方法实现起来比较复杂,但代码相对较少,拆分一下有以下几步。
获取旧值:
function set(target, key, value, receiver) {
// 获取旧值
let oldValue = target[key];
// ...
};
这里的旧值指的是target[ket]
的值。
判断是否只读:
function set(target, key, value, receiver) {
// ...
// 如果旧值是只读的,并且是 ref,并且新值不是 ref,那么直接返回 false,代表设置失败
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
return false;
}
// ...
};
如果只读的话ref的值是不能修改的,所以直接返回false。
判断是否是浅的:
function set(target, key, value, receiver) {
// ...
// 如果不是浅的
if (!shallow) {
// ...
}
// ...
};
在判断是否是浅的时,参数是通过闭包下来的。如果不是浅层响应,那么就会获取旧值的原始值和新值的原始值:
function set(target, key, value, receiver) {
// ...
// 如果不是浅的
if (!shallow) {
// 如果新值不是浅的,并且不是只读的
if (!isShallow(value) && !isReadonly(value)) {
// 获取旧值的原始值
oldValue = toRaw(oldValue);
// 获取新值的原始值
value = toRaw(value);
}
}
// ...
};
在这里需要先判断新值是不是浅层响应的且不是只读。如果是,就不需要获取原始值,因为这时的新值就是原始值。
如果是浅层响应的,那就说明这个响应式对象的元素只有一层响应式,只关心当前对象的响应式。所以不用获取原始值,会直接覆盖。
deleteProperty
function deleteProperty(target, key) {
// 当前对象是否有这个 key
const hadKey = hasOwn(target, key);
// 旧值
const oldValue = target[key];
// 通过 Reflect.deleteProperty 删除属性
const result = Reflect.deleteProperty(target, key);
// 如果删除成功,并且当前对象有这个 key,那么就触发 delete 事件
if (result && hadKey) {
trigger(target, "delete" /* TriggerOpTypes.DELETE */, key, undefined, oldValue);
}
// 返回结果,这个结果为 boolean 类型,代表是否删除成功
return result;
}
deleteProperty
方法就是通过Reflect.deleteProperty
来删除属性,再通过trigger
触发delete
事件,最后返回是否删除成功。
has
function has$1(target, key) {
// 通过 Reflect.has 判断当前对象是否有这个 key
const result = Reflect.has(target, key);
// 如果当前对象不是 Symbol 类型,或者当前对象不是内置的 Symbol 类型,那么就触发 has 事件
if (!isSymbol(key) || !builtInSymbols.has(key)) {
track(target, "has" /* TrackOpTypes.HAS */, key);
}
// 返回结果,这个结果为 boolean 类型,代表当前对象是否有这个 key
return result;
}
has
直接通过Reflect.has
判断当前对象是否有key,然后通过track
触发has
事件,最后返回是否有key的结果。
ownKeys
function ownKeys(target) {
// 直接触发 iterate 事件
track(target, "iterate" /* TrackOpTypes.ITERATE */, isArray(target) ? 'length' : ITERATE_KEY);
// 通过 Reflect.ownKeys 获取当前对象的所有 key
return Reflect.ownKeys(target);
}
ownKeys
方法的实现也是比较简单的,直接触发iterate
事件,然后通过Reflect.ownKeys
获取当前对象的所有 key,最后返回这些 key;
注意点在于对数组的特殊处理,如果当前对象是数组的话,那么就会触发length
的iterate
事件,如果不是数组的话,那么就会触发ITERATE_KEY
的iterate
事件。
Effect
在了解了reactive
方法后,再来看下effect
方法。effect
实际上就是创建一个副作用函数,这个函数会在依赖的数据发生变化时执行。来看下它是怎么实现的:
function effect(fn, options) {
// 如果 fn 对象上有 effect 属性
if (fn.effect) {
// 那么就将 fn 替换为 fn.effect.fn
fn = fn.effect.fn;
}
// 创建一个响应式副作用函数
const _effect = new ReactiveEffect(fn);
// 如果有配置项
if (options) {
// 将配置项合并到响应式副作用函数上
extend(_effect, options);
// 如果配置项中有 scope 属性(该属性的作用是指定副作用函数的作用域)
if (options.scope)
// 那么就将 scope 属性记录到响应式副作用函数上(类似一个作用域链)
recordEffectScope(_effect, options.scope);
}
// 如果没有配置项,或者配置项中没有 lazy 属性,或者配置项中的 lazy 属性为 false
if (!options || !options.lazy) {
// 那么就执行响应式副作用函数
_effect.run();
}
// 将 _effect.run 的 this 指向 _effect
const runner = _effect.run.bind(_effect);
// 将响应式副作用函数赋值给 runner.effect
runner.effect = _effect;
// 返回 runner
return runner;
}
在这段代码中有两个重点:
- 创建一个响应式副作用函数
const_effect=new ReactiveEffect(fn)
。 - 返回一个runner函数,可以通过这个函数来执行副作用函数。
收集依赖
在了解了reactive
和effect
之后,再来看下收集依赖。
在学习响应式系统时经常能听到在getter
中收集依赖,在setter
中触发依赖。现在看下如何收集依赖。
track
在getter
中有这样一段代码:
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
// ...
// 如果不是只读的,就会收集依赖
if (!isReadonly) {
track(target, "get" /* TrackOpTypes.GET */, key);
}
// ...
return res;
};
}
这里调用了track
方法来收集依赖,该方法实现如下:
const targetMap = new WeakMap();
/**
* 收集依赖
* @param target 指向的对象
* @param type 操作类型
* @param key 指向对象的 key
*/
function track(target, type, key) {
// 如果 shouldTrack 为 false,并且 activeEffect 没有值的话,就不会收集依赖
if (shouldTrack && activeEffect) {
// 如果 targetMap 中没有 target,就会创建一个 Map
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
// 如果 depsMap 中没有 key,就会创建一个 Set
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = createDep()));
}
// 将当前的 ReactiveEffect 对象添加到 dep 中
const eventInfo = {
effect: activeEffect,
target,
type,
key
};
// 如果 dep 中没有当前的 ReactiveEffect 对象,就会添加进去
trackEffects(dep, eventInfo);
}
}
这里targetMap
是一个weakMap
,它的键是target
,值是Map
,而这个Map
的键是key
,值是Set
。
这里使用weakMap
是因为它是一个弱引用的键值对集合,这意味着如果一个对象作为weakMap
的键,且没有其他引用指向该对象,那么这个对象可以被垃圾回收机制自动回收。可以有效的提升性能,释放内存。
这里还有两个熟人:shouldTrack
和activeEffect
。shouldTrack
是用来控制是否收集依赖的;activeEffect
指向当前正在执行的副作用函数。
track
方法是利用targetMap
记录下target
和key
。这意味着在操作target
的key
时,就会收集依赖,target
和key
就会被记录到targetMap
中:
const obj = {
a: 1,
b: 2
};
const targetMap = new WeakMap();
// 我在操作 obj.a 的时候,就会收集依赖
obj.a;
// 这个时候,targetMap 中就会记录下 obj 和 a
let depsMap = targetMap.get(obj);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
// createDep 实现很简单,就不在讲解的代码里面单独写出来了,具体就是一个 Set,多了两个属性,w 和 n
const createDep = (effects) => {
const dep = new Set(effects);
dep.w = 0; // 指向的是 watcher 对象的唯一标识
dep.n = 0; // 指向的是不同的 dep 的唯一标识
return dep;
};
let dep = depsMap.get("a");
if (!dep) {
depsMap.set("a", (dep = createDep()));
}
// dep 就是一个 Set,里面存放的就是当前的 ReactiveEffect 对象
dep.add(activeEffect);
trigger
trigger
方法也很简单,就是触发依赖,实现如下:
/**
* 触发依赖
* @param target 指向的对象
* @param type 操作类型
* @param key 指向对象的 key
* @param newValue 新值
* @param oldValue 旧值
* @param oldTarget 旧的 target
*/
function trigger(target, type, key, newValue, oldValue, oldTarget) {
// 获取 targetMap 中的 depsMap
const depsMap = targetMap.get(target);
if (!depsMap) {
// never been tracked
return;
}
// 创建一个数组,用来存放需要执行的 ReactiveEffect 对象
let deps = [];
// 如果 type 为 clear,就会将 depsMap 中的所有 ReactiveEffect 对象都添加到 deps 中
if (type === "clear" /* TriggerOpTypes.CLEAR */) {
// 执行所有的 副作用函数
deps = [...depsMap.values()];
}
// 如果 key 为 length ,并且 target 是一个数组
else if (key === 'length' && isArray(target)) {
// 修改数组的长度,会导致数组的索引发生变化
// 但是只有两种情况,一种是数组的长度变大,一种是数组的长度变小
// 如果数组的长度变大,那么执行所有的副作用函数就可以了
// 如果数组的长度变小,那么就需要执行索引大于等于新数组长度的副作用函数
const newLength = Number(newValue);
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= newLength) {
deps.push(dep);
}
});
}
// 其他情况
else {
// key 不是 undefined,就会将 depsMap 中 key 对应的 ReactiveEffect 对象添加到 deps 中
// void 0 就是 undefined
if (key !== void 0) {
deps.push(depsMap.get(key));
}
// 执行 add、delete、set 操作时,就会触发的依赖变更
switch (type) {
// 如果 type 为 add,就会触发的依赖变更
case "add" /* TriggerOpTypes.ADD */:
// 如果 target 不是数组,就会触发迭代器
if (!isArray(target)) {
// ITERATE_KEY 再上面介绍过,用来标识迭代属性
// 例如:for...in、for...of,这个时候依赖会收集到 ITERATE_KEY 上
// 而不是收集到具体的 key 上
deps.push(depsMap.get(ITERATE_KEY));
// 如果 target 是一个 Map,就会触发 MAP_KEY_ITERATE_KEY
if (isMap(target)) {
// MAP_KEY_ITERATE_KEY 同上面的 ITERATE_KEY 一样
// 不同的是,它是用来标识 Map 的迭代器
// 例如:Map.prototype.keys()、Map.prototype.values()、Map.prototype.entries()
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
// 如果 key 是一个数字,就会触发 length 依赖
else if (isIntegerKey(key)) {
// 因为数组的索引是可以通过 arr[0] 这种方式来访问的
// 也可以通过这种方式来修改数组的值,所以会触发 length 依赖
deps.push(depsMap.get('length'));
}
break;
// 如果 type 为 delete,就会触发的依赖变更
case "delete" /* TriggerOpTypes.DELETE */:
// 如果 target 不是数组,就会触发迭代器,同上面的 add 操作
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY));
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
break;
// 如果 type 为 set,就会触发的依赖变更
case "set" /* TriggerOpTypes.SET */:
// 如果 target 是一个 Map,就会触发迭代器,同上面的 add 操作
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY));
}
break;
}
}
// 创建一个 eventInfo 对象,主要是调试的时候会用到
const eventInfo = {
target,
type,
key,
newValue,
oldValue,
oldTarget
};
// 如果 deps 的长度为 1,就会直接执行
if (deps.length === 1) {
if (deps[0]) {
{
triggerEffects(deps[0], eventInfo);
}
}
}
else {
// 如果 deps 的长度大于 1,这个时候会组装成一个数组,然后再执行
// 这个时候调用就类似一个调用栈
const effects = [];
for (const dep of deps) {
if (dep) {
effects.push(...dep);
}
}
{
triggerEffects(createDep(effects), eventInfo);
}
}
}
当我们修改数据时,就会触发依赖,再执行依赖中的副作用函数。在这里主要是收集需要执行的副作用函数,再到triggerEffects
函数中执行。
来看下triggerEffects
:
function triggerEffects(dep, debuggerEventExtraInfo) {
// 如果 dep 不是数组,就会将 dep 转换成数组,因为这里的 dep 可能是一个 Set 对象
const effects = isArray(dep) ? dep : [...dep];
// 执行 computed 依赖
for (const effect of effects) {
if (effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo);
}
}
// 执行其他依赖
for (const effect of effects) {
if (!effect.computed) {
triggerEffect(effect, debuggerEventExtraInfo);
}
}
}
这里只是转换dep
,再执行computed
依赖和其他依赖,主要是看triggerEffect
函数:
function triggerEffect(effect, debuggerEventExtraInfo) {
// 如果 effect 不是 activeEffect,或者 effect 允许递归,就会执行
if (effect !== activeEffect || effect.allowRecurse) {
// 如果 effect.onTrigger 存在,就会执行,只有开发模式下才会执行
if (effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo));
}
// 如果 effect 是一个调度器,就会执行 scheduler
if (effect.scheduler) {
effect.scheduler();
}
// 否则直接执行 effect.run()
else {
effect.run();
}
}
}
这里的effect.scheduler
是一个调度器,允许用户自定义调用副作用函数的实际。effect.run
就是调用副作用函数。
总结
讲了这么多,实际上响应式原理就四步:
- 创建一个响应式对象。
- 创建一个副作用函数。
- 访问响应式对象,触发依赖收集。
- 修改响应式对象,出发依赖执行。
这四步都是围绕effect
函数,reactive
函数,track
函数,trigger
函数这四个函数。
这样的设计让整个响应式系统变得简单且容易维护。