响应式对象 getter 相关逻辑就是做依赖收集
首先定义了一个响应式对象,拥有 getter 进行读取数据的监听,在 getter 中会收集当前计算的 Watcher,然后把 Watcher 作为一个订阅者,在数据更改的时候会触发 setter。在 getter 之前会初始化一个 Dep 实例,这个 dep 实例会做依赖收集,Dep 实例是对 Watcher 实例的管理,Wacher 实例定义了 Dep 相关的属性与方法,比如增加依赖和清除依赖。new Vue 后 mountComponent,实例化一个渲染 watcher,进入 watcher 构造函数,会执行 vm.updateComponent,然后是在 vm._render 的过程中触发所有数据的 getter,Watcher 实例同时也定义了两个数组,分别是上一次添加的 Dep 实例数组和新添加的 Dep 实例数组,数组都有各自对应的 id,目的是为了每次添加完新的订阅,会移除旧的订阅,达到节约资源的目的。
依赖收集就是订阅数据变化的 watcher 收集,依赖收集的目的是为了当这些响应式数据发生变化,触发他们的 setter 的时候,能知道应该通知哪些订阅者去做响应的处理
代码块
- defineReactive
定义了一个响应式对象,给对象动态添加 getter 和 setter
// src/core/observer/index.js
//
export function defineReactive(obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean) {
// 实例化一个Dep实例
const dep = new Dep();
const property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return;
}
// cater for pre-defined getter/setters
const getter = property && property.get;
const setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
let childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val;
// 依赖收集的过程
// Dep是一个类,依赖收集的值,建立数据与Wachter的桥梁
// Dep.target是一个全局的Watcher
if (Dep.target) {
// 是一个Dep实例
// 通过dep.depend做依赖收集
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value;
}
// ...
});
}
- Dep
Dep 是对 watcher 的一种管理
// src/core/observer/dep.js
import type Watcher from './watcher';
import { remove } from '../util/index';
let uid = 0;
export default class Dep {
// 静态属性target,是全局唯一一个Watcher
// 同一时间智能有一个全局Watcher被计算
static target: ?Watcher;
id: number;
// 自身属性subs也是Watcher的数组
subs: Array<Watcher>;
constructor() {
this.id = uid++;
this.subs = [];
}
addSub(sub: Watcher) {
this.subs.push(sub);
}
removeSub(sub: Watcher) {
remove(this.subs, sub);
}
depend() {
if (Dep.target) {
// addDep在Wachter中定义的
Dep.target.addDep(this);
}
}
notify() {
// stabilize the subscriber list first
const subs = this.subs.slice();
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
}
}
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null;
const targetStack = [];
export function pushTarget(_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target);
Dep.target = _target;
}
export function popTarget() {
Dep.target = targetStack.pop();
}
Watcher
定义了一些和 Dep 相关的属性:Dep 实例数组,实例数组 id,定义了一些原型方法
在 vm._render()过程中会触发所有数据的 getter
// src/core/observer/watcher.js
let uid = 0;
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
computed: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
dep: Dep;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor(vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean) {
this.vm = vm;
if (isRenderWatcher) {
vm._watcher = this;
}
vm._watchers.push(this);
// options
if (options) {
this.deep = !!options.deep;
this.user = !!options.user;
this.computed = !!options.computed;
this.sync = !!options.sync;
this.before = options.before;
} else {
this.deep = this.user = this.computed = this.sync = false;
}
// 定义了一些和Dep相关的属性
this.cb = cb;
this.id = ++uid; // uid for batching
this.active = true;
this.dirty = this.computed; // for computed watchers
// 属性1,Dep实例数组
this.deps = [];
// 属性2,Dep实例数组
this.newDeps = [];
// 属性3,Dep实例数组id
this.depIds = new Set();
// 属性4,Dep实例数组
this.newDepIds = new Set();
this.expression = process.env.NODE_ENV !== 'production' ? expOrFn.toString() : '';
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = function() {};
process.env.NODE_ENV !== 'production' && warn(`Failed watching path: "${expOrFn}" ` + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm);
}
}
if (this.computed) {
this.value = undefined;
this.dep = new Dep();
} else {
this.value = this.get();
}
}
// 原型方法1
get() {
// pushTarget定义
// 对Dep.target赋值为当前的渲染watcher并压栈
// export function pushTarget(_target: ?Watcher) {
// if (Dep.target) targetStack.push(Dep.target);
// Dep.target = _target;
// }
pushTarget(this);
let value;
const vm = this.vm;
try {
// this.getter就是updateComponent 函数
// 实际上执行vm._update(vm._render(), hydrating)
// 首先执行vm_render方法,生成渲染VNode,并对vm上的数据访问,触发了数据对象的getter。
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`);
} else {
throw e;
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
// 递归访问value,触发所有子项getter
traverse(value);
}
// popTarget定义
// 收集完把Dep.target恢复成上一个状态,
// export function popTarget() {
// Dep.target = targetStack.pop();
// }
popTarget();
this.cleanupDeps();
}
return value;
}
// 原型方法2
// 每个对象值getter都持有一个dep,触发getter的时候会调用dep.depend,也就是执行Dep.target.addDep(this)
addDep(dep: Dep) {
const id = dep.id;
// 做一些逻辑判断,保证同意数据不会多次添加
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
// 然后执行addSub,也会执行this.subs.push(sub)
// 目的是为了后续数据变化时候通知subs做准备
dep.addSub(this);
}
}
}
// 原型方法3
// 依赖清空的方法
// Vue是数据驱动的,每次数据变化都会重新render,vm._render再次触发getters,所以Watcher在构造函数初始化的时候初始化了两个数组newDeps和deps ,newDeps表示新添加的Dep实例数组,deps表示上一次添加的Dep实例数组。
// 在cleanupDeps,先遍历上一次添加的Dep实例数组,也就是deps,移除对dep的订阅,然后把newDepIds 和 depIds 交换,把两个数组清空
// cleanupDeps的目的是为了 a,b两个v-if的模版,先if到a模板,a模块触发某种情况,到了b模板,为了节约资源,cleanupDeps上次添加的Dep实例,去if b模板,if模板是一个vm._render的过程,cleanupDeps清除了订阅回调,就在if b模板的时候不再if a模板
// cleanupDeps发现新的一轮订阅没有订阅,而且老的订阅数组里有该条订阅记录,就把该订阅者删掉,一旦没有订阅变化,去修改他不会触发重新渲染。
cleanupDeps() {
let i = this.deps.length;
while (i--) {
const dep = this.deps[i];
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this);
}
}
let tmp = this.depIds;
// 把newDepIds 和 depIds 交换
this.depIds = this.newDepIds;
this.newDepIds = tmp;
this.newDepIds.clear();
tmp = this.deps;
this.deps = this.newDeps;
this.newDeps = tmp;
this.newDeps.length = 0;
}
// ...
}
共用代码
- mountComponent
数据对象的访问会触发 getter 方法,数据什么时候访问呢?
// src/core/instance/lifecycle.js
// 当去实例化一个渲染watcher的时候先进入Watcher构造函数,执行this.get()方法
updateComponent = () => {
vm._update(vm._render(), hydrating);
};
new Watcher(
vm,
updateComponent,
noop,
{
before() {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate');
}
}
},
true /* isRenderWatcher */
);
确认评论邮箱
邀请制评论