响应式原理
回顾下 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
并执行