# 响应式原理
回顾下 2.0 响应式原理实现过程:
Vue 在初始化的时候会遍历
data
数据,对于每个属性执行defineReactive
方法,这里方法主要做以下两个事情:实例化一个事件器
Dep
使用
Obejct.definedPropery()
方法代理属性并添加set
和get
方法,get
中添加了收集Watcher
的逻辑,而set
则是包含了执行Watcher
的逻辑
在组件渲染的时候,会实例化一个
Watcher
实例,并将当前Watcher
实例到全局属性Dep.target
中,之后在执行渲染操作的时候会读取data
的属性, 并被get
劫持,get
方法中将保存在全局的Watcher
收集到dep
中之后当
data
属性被更改时,会被set
劫持,然后执行Watcher
中的更新方法
# Vue3.0
3.0 中改用了 Proxy 方法,以 data
属性为例来品一下怎么玩的
源码追踪: mountComponent(n2, container) => setupComponent(instance) => setupStatefulComponent() => finishComponentSetup(instance) => applyOptions(instance, Component) => resolveData(instance, dataOptions, publicThis)
resolveData
就是处理 data
属性的地方
function resolveData(instance, dataFn, publicThis) {
// data属性只能是函数
if ( !isFunction(dataFn)) {
warn(`The data option must be a function. ` +
`Plain object usage is no longer supported.`);
}
// 获取 data 的值
const data = dataFn.call(publicThis, publicThis);
// data函数返回值不能是 Promise
if ( isPromise(data)) {
warn(`data() returned a Promise - note data() cannot be async; If you ` +
`intend to perform data fetching before component renders, use ` +
`async setup() + <Suspense>.`);
}
// data函数返回值必需是对象
if (!isObject(data)) {
warn(`data() should return an object.`);
}
else if (instance.data === EMPTY_OBJ) {
// 设置代理
instance.data = reactive(data);
}
else {
// existing data: this is a mixin or extends.
extend(instance.data, data);
}
}
上文对 data
属性和返回值做了一些校验后,执行 reactive(data)
方法设置代理
function reactive(target) {
// if trying to observe a readonly proxy, return the readonly version.
if (target && target["__v_isReadonly" /* IS_READONLY */]) {
return target;
}
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers);
}
// reactive 实际上执行的是 `createReactiveObject` 方法
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
if (!isObject(target)) {
{
console.warn(`value cannot be made reactive: ${String(target)}`);
}
return target;
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (target["__v_raw" /* RAW */] &&
!(isReadonly && target["__v_isReactive" /* IS_REACTIVE */])) {
return target;
}
// target already has corresponding Proxy
// 使用 new WeakMap() 保存当前代理
const proxyMap = isReadonly ? readonlyMap : reactiveMap;
const existingProxy = proxyMap.get(target);
// 如果已经存在则是直接返回
if (existingProxy) {
return existingProxy;
}
// only a whitelist of value types can be observed.
const targetType = getTargetType(target);
if (targetType === 0 /* INVALID */) {
return target;
}
const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
proxyMap.set(target, proxy);
return proxy;
}
设置代理的地方: const proxy = new Proxy(target, baseHandlers)
, baseHandlers
就是 Proxy 的处理器对象,根据上下文找到 baseHandlers
的定义:
const mutableHandlers = {
get,
set,
deleteProperty,
has,
ownKeys
}
const get = /*#__PURE__*/ createGetter();
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) { // 设置 get 方法
// 如果当前访问 __v_isReactive 返回 true
if (key === "__v_isReactive" /* IS_REACTIVE */) {
return !isReadonly;
}
// 如果当前访问 __v_isReadonly 返回 false
else if (key === "__v_isReadonly" /* IS_READONLY */) {
return isReadonly;
}
// 如果当前访问 __v_raw 从缓存中找对应的值,先不关心这个上
else if (key === "__v_raw" /* RAW */ &&
receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)) {
return target;
}
// 如果当前访问的数组中的属性,且是 ['includes', 'indexOf', 'lastIndexOf'] 中的一种,则调用重写的方法
const targetIsArray = isArray(target);
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver);
}
// 获取属性对应的值
const res = Reflect.get(target, key, receiver);
if (isSymbol(key)
? builtInSymbols.has(key)
: key === `__proto__` || key === `__v_isRef`) {
return res;
}
if (!isReadonly) {
// 执行 track 方法,收集 effect 事件方法
track(target, "get" /* GET */, key);
}
if (shallow) {
return res;
}
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
const shouldUnwrap = !targetIsArray || !isIntegerKey(key);
return shouldUnwrap ? res.value : res;
}
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res);
}
return res;
};
}
const set = /*#__PURE__*/ createSetter()
function createSetter(shallow = false) {
return function set(target, key, value, receiver) {
const oldValue = target[key]; // 得到旧的值
if (!shallow) {
value = toRaw(value);
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value;
return true;
}
}
const hadKey = isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, "add" /* ADD */, key, value);
}
else if (hasChanged(value, oldValue)) {
// 触发收集的事件,进行组件更新
trigger(target, "set" /* SET */, key, value, oldValue);
}
}
return result;
};
}
# 过程分析
# 收集依赖
当组件访问 data
中的属性时被 get
方法劫持,执行下面语句:
const res = Reflect.get(target, key, receiver)
if (!isReadonly) {
track(target, "get" /* GET */, key);
}
// target 代理对象 属性
// type get
// key 要访问的属性
function track(target, type, key) {
if (!shouldTrack || activeEffect === undefined) {
return;
}
// 通过当前对象获取 dep
let depsMap = targetMap.get(target);
if (!depsMap) {
// 如果没有就添加
targetMap.set(target, (depsMap = new Map()));
}
// 通过属性获取 dep
let dep = depsMap.get(key);
if (!dep) {
// 如果没有添加一个 dep
depsMap.set(key, (dep = new Set()));
}
// 当前 dep 不包含 activeEffect 则添加到 dep 中
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
// 当前的dep 收集到activeEffect.deps 中
activeEffect.deps.push(dep);
if ( activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
});
}
}
}
上面代码可以看到的信息:
属性被访问时,会执行
track
方法做事件收集工作activeEffect
是一个全局属性,记录当前正被执行的effect
, 其实这个effect
对应 vue2.0 中的Watcher
每个代理对象都通过
WeakMap
结构保存着这个对象的 收集器集合depsMap
每个对象的属性再通过一步中的
depsMap
保存着对应的dep
,depsMap
是一个Map
结构
以上就完成的事件收集的工作
# 触发更新
当更新响应的数据时,将被 set
方法劫持, 执行 set
中的 trigger
方法
trigger(target, "set" /* SET */, key, value, oldValue);
function trigger(target, type, key, newValue, oldValue, oldTarget) {
// 通过对象获取当前 收集器集合 depMap
const depsMap = targetMap.get(target);
if (!depsMap) { // 如果没有,说明对象没有添加响应机制
// never been tracked
return;
}
const effects = new Set(); // 保存当前收集到且要执行的 effect
const add = (effectsToAdd) => {
// 将收集到的 effect 添加到 effects 集合中
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect);
}
});
}
};
// 略
if (type === "clear" /* CLEAR */) {
// collection being cleared
// trigger all effects for target
depsMap.forEach(add);
}
// 略
else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= newValue) {
add(dep);
}
});
}
else {
// 改变属性时,将执行下面语句
if (key !== void 0) {
// 根据 key 获取收集到的 dep 然后执行 add 方法
add(depsMap.get(key));
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case "add" /* ADD */:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY));
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
else if (isIntegerKey(key)) {
// new index added to array -> length changes
add(depsMap.get('length'));
}
break;
case "delete" /* DELETE */:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY));
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
break;
case "set" /* SET */:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY));
}
break;
}
}
const run = (effect) => {
if ( effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
});
}
if (effect.options.scheduler) {
// 执行每个 effects 的scheduler 方法
effect.options.scheduler(effect);
}
else {
effect();
}
};
// 遍历 effects 执行 run 方法
effects.forEach(run);
}
effect.options.scheduler
就是下面的 queueJob
方法
function queueJob(job) {
if ((!queue.length ||
!queue.includes(job, isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex)) &&
job !== currentPreFlushParentJob) {
queue.push(job);
queueFlush();
}
}
queueJob
方法就是将当前 job(effect)
添加到 queue
队列中,然后执行 queueFlush
方法开始一次微任务的添加,这样在事件循环处理事件队列时就可以执行 effect
方法,具体执行的代码为:
function queueFlush() {
if (!isFlushing && !isFlushPending) {
isFlushPending = true;
currentFlushPromise = resolvedPromise.then(flushJobs);
}
}
function flushJobs(seen) {
isFlushPending = false;
isFlushing = true;
{
seen = seen || new Map();
}
flushPreFlushCbs(seen);
// 对收集到的 effect 进行排序
queue.sort((a, b) => getId(a) - getId(b));
try {
for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
const job = queue[flushIndex];
if (job) {
if (true) {
checkRecursiveUpdates(seen, job);
}
// 调用 callWithErrorHandling 执行每一个 effect
callWithErrorHandling(job, null, 14 /* SCHEDULER */);
}
}
}
finally {
flushIndex = 0;
queue.length = 0;
flushPostFlushCbs(seen);
isFlushing = false;
currentFlushPromise = null;
// some postFlushCb queued jobs!
// keep flushing until it drains.
if (queue.length || pendingPostFlushCbs.length) {
flushJobs(seen);
}
}
}
// 通过 callWithErrorHandling 执行每个 effect
function callWithErrorHandling(fn, instance, type, args) {
let res;
try {
// 执行每一个 effect 方法
res = args ? fn(...args) : fn();
}
catch (err) {
handleError(err, instance, type);
}
return res;
}
更新依赖的过程总结如下:
属性变更时,触发
trigger
方法,注意这个时间的type=set
从
depsMap
取出当前属性收集的effect
集合遍历这些
effect
,执行每个effect.options.scheduler(effect)
方法,将efftct
添加到queue
中同时添加一个微任务到队列当中
执行微任务时,对
queue
中的effect
进行排序,然后分别执行effect
方法
# 关于深度对象的监听
Counter: {{ counter.cou }}
data() {
return {
counter: {
cou: 1
}
}
},
此时的处理步骤为:
首先仍然会通过
resolveData(instance, dataOptions, publicThis) =>reactive(data) => createReactiveObject
, 给当前data
的值做proxy
代理渲染组件访问
data
对象中的counter
的属性,调用track(target, "get" /* GET */, key)
,给当前属性counter
添加事件effect
如果判断到
counter
属性的值是个对象,执行isReadonly ? readonly(res) : reactive(res)
,相当于回到第一步,给当前counter
值做proxy
代理之后访问
counter
对象中访问cou
的属性,又被get
劫持,调用track(target, "get" /* GET */, key)
,给当前属性cou
添加事件effect
可以看到对于深层的对象,仍然是需要对子对象做代理的,跟 vue2.0 一样,只是设置的代理的时机不同了
vue2.0 是初始化组件的时候遍历
data
及子属性对象,添加Object.defineProperry
代理vue3.0 只有
data
属性是在一开始的时候做proxy
代理,之后是触发get
后,再对子对象添加proxy
代理
加个 proxy
粟子理解一下:
var handler = {
get: function(obj, prop) {
console.log(obj)
return obj[prop]
}
};
var p = new Proxy({a: {b: 34}}, handler)
//输入 p.a
//触发 console.log => - {a: {b: 34}}
//输出 {a: {b: 34}}
//输入 p.a.b
//触发 console.log => - {a: {b: 34}}
//输出 34
上面例子说明 prosy
只能代理第一层的属性
# 如果是添加属性呢?
假设我们给上面粟子中的 counter
对象在后期添加一个属性,Vue 又是怎么处理的呢?
this.counter.abc = 3
执行上面的语句时代码运行过程:
首先得访问 this.counter
属性,此时也会被 get
方法劫持,并执行 track(target, "get" /* GET */, key);
方法,但因为此时全局环境没有 activeEffect
事件,所以没有执行任何事件收集的动作
function track(target, type, key) {
if (!shouldTrack || activeEffect === undefined) {
return;
}
}
然后 this.counter.abc = 3
时被 set
方法劫持
function set(target, key, value, receiver) {
// 取旧值为 undefined
const oldValue = target[key];
if (!shallow) {
value = toRaw(value); // 当前例子为 3
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value;
return true;
}
}
// 因为之前这个属性,hadKey 为false
const hadKey = isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key);
// 给 target 添加这个属性
const result = Reflect.set(target, key, value, receiver);
if (target === toRaw(receiver)) {
// hadKey 为 false,所以执行 trigger 方法
if (!hadKey) {
trigger(target, "add" /* ADD */, key, value);
}
else if (hasChanged(value, oldValue)) {
trigger(target, "set" /* SET */, key, value, oldValue);
}
}
return result;
};
// 注意此时的 type = add
function trigger(target, type, key, newValue, oldValue, oldTarget) {
// 获取 target 收集器集合
const depsMap = targetMap.get(target);
if (!depsMap) {
// never been tracked
return;
}
const effects = new Set();
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect);
}
});
}
};
if (type === "clear" /* CLEAR */) {
// collection being cleared
// trigger all effects for target
depsMap.forEach(add);
}
else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= newValue) {
add(dep);
}
});
}
else {
// key 有效
if (key !== void 0) {
// 但是因为当前 key 是新添加的,所以没有当前属性对应的 dep,我们继续往下走
add(depsMap.get(key));
}
switch (type) {
case "add" /* ADD */:
// 当前 type 为 add 所以执行以下语句
if (!isArray(target)) {
// 取出 ITERATE_KEY 收集的 effect,之后的步骤添加到微任务队列触发 effect 事件,进行更新
add(depsMap.get(ITERATE_KEY));
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
else if (isIntegerKey(key)) {
// new index added to array -> length changes
add(depsMap.get('length'));
}
break;
case "delete" /* DELETE */:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY));
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
break;
case "set" /* SET */:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY));
}
break;
}
}
const run = (effect) => {
if ( effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
});
}
if (effect.options.scheduler) {
effect.options.scheduler(effect);
}
else {
effect();
}
};
effects.forEach(run);
}
总结一下当给对象添加一个属性时的大致过程:
首先给当前
target
添加新的属性并赋值调用
trigger(target, "add" /* ADD */, key, value);
,从ITERATE_KEY
中取出effect
事件加入到队列
ITERATE_KEY
是通过 proxy
劫持 ownKeys
属性添加的事件收集
function ownKeys(target) {
debugger
track(target, "iterate" /* ITERATE */, isArray(target) ? 'length' : ITERATE_KEY);
return Reflect.ownKeys(target);
}
如果是对象 ITERATE_KEY
属性, 数组的话就添加 length
属性,至少是怎么触发的 ownKeys
方法 没找到位置...
- 当组件更新的时候就会重新取新增的属性,并给属性添加
effect
事件收集
# 数组类型的更新
将上面的例子改成数据类型: counter: [1,2,3]
,当我们使用 counter.push(4)
时看下代码的运行
return function get(target, key, receiver) {
// 略
const targetIsArray = isArray(target);
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver);
}
const res = Reflect.get(target, key, receiver);
// 略
return res;
};
// arrayInstrumentations 方法定义
const arrayInstrumentations = {};
['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
const method = Array.prototype[key];
arrayInstrumentations[key] = function (...args) {
const arr = toRaw(this);
for (let i = 0, l = this.length; i < l; i++) {
track(arr, "get" /* GET */, i + '');
}
// we run the method using the original args first (which may be reactive)
const res = method.apply(arr, args);
if (res === -1 || res === false) {
// if that didn't work, run it again using raw values.
return method.apply(arr, args.map(toRaw));
}
else {
return res;
}
};
});
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(key => {
const method = Array.prototype[key];
arrayInstrumentations[key] = function (...args) {
pauseTracking();
const res = method.apply(this, args);
resetTracking();
return res;
};
});
首先当访问 push
属性时,也会被 proxy
劫持,根据下文中的代码,使用将会执行 return Reflect.get(arrayInstrumentations, key, receiver);
arrayInstrumentations
是重写后的一些数组方法的集合,所以将返回 arrayInstrumentations.push
方法
之后执行 push(4)
时, 将执行以下语句
// 实际做的事情是 trackStack.push(false); 给 trackStack 添加一个 false 标识
pauseTracking();
// 执行真正的 Array.push 方法,此时应该等价于 counter[counter.length = 3] = 4 ,所以在获取属性 `3` 时, 会被 set 劫持
const res = method.apply(this, args);
resetTracking();
return res;
上面代码主要做的事情
pauseTracking():实际做的事情是
trackStack.push(false)
; 给trackStack
添加一个false
标识, 暂停依赖收集,也就意味在此期间执行track
时,不会去收集依赖function track(target, type, key) { if (!shouldTrack || activeEffect === undefined) { return; } }
当执行
const res = method.apply(this, args)
, 会被set
劫持
function createSetter(shallow = false) {
// 当前例子 key = 3
// value = 4
return function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, "set" /* SET */, key, value, oldValue);
return result;
};
}
function trigger(target, type, key, newValue, oldValue, oldTarget) {
const depsMap = targetMap.get(target);
const effects = new Set();
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect);
}
});
}
};
if (type === "clear" /* CLEAR */) {
depsMap.forEach(add);
}
else if (key === 'length' && isArray(target)) {
}
else {
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case "add" /* ADD */:
if (!isArray(target)) { }
else if (isIntegerKey(key)) {
// new index added to array -> length changes
add(depsMap.get('length'));
}
break;
}
}
effects.forEach(run);
}
跟之前 set
流程类型类型,之后在 trigger
方法根据 length
取出当前对象收集的 effect
,添加到微任务队列中进行视图更新
那么使用数组下标更改数组也是同理
# 总结
其实思路还是跟 vue2.0 的差不多
Vue 在初始化的时候使用
proxy
方法代理,关于收集器两个版本的区别Vue2.0 会为每个属性实例化一个事件收集器
Dep
Vue3.0 则通过全局变量
const targetMap = new WeakMap();
来做事件收集容器,targetMap
的key
是代理对象,value
是depsMap
(一个收集事件容器的容器)depsMap
是new Map()
结构,代理对象的每个属性收集的事件将存储在这个depsMap
中,所以找属性收集的事件时,先根据这个属性所属对象从targetMap
找depsMap
, 在根据具体属性从depsMap
找事件在组件渲染的时候,会生成一个
effect
方法,并将当前effect
保存到全局activeEffect
中,之后在执行渲染操作的时候会读取data
的属性, 并被get
劫持,get
方法中将保存在全局的activeEffect
收集到depsMap
中之后当
data
属性被更改时,会被set
劫持,从depsMap
取出effect
并执行