Vue源码解读之Dep,Observer和Watcher

时间:2022-01-30 16:44:41

在解读Dep,Observer和Watcher之前,首先我去了解了一下Vue的数据双向绑定,即MVVM,学习于:https://blog.csdn.net/u013321...
以及关于Observer和watcher的学习来自于:https://www.jb51.net/article/...

整体过程

Vue实例化一个对象的具体过程如下:

  1. 新创建一个实例后,Vue调用compile将el转换成vnode。
  2. 调用initState, 创建props, data的钩子以及其对象成员的Observer(添加getter和setter)。
  3. 执行mount挂载操作,在挂载时建立一个直接对应render的Watcher,并且编译模板生成render函数,执行vm._update来更新DOM。
  4. 每当有数据改变,都将通知相应的Watcher执行回调函数,更新视图。

    • 当给这个对象的某个属性赋值时,就会触发set方法。
    • set函数调用,触发Dep的notify()向对应的Watcher通知变化。
    • Watcher调用update方法。

Vue源码解读之Dep,Observer和Watcher

在这个过程中:

  1. Observer是用来给数据添加Dep依赖。
  2. Dep是data每个对象包括子对象都拥有一个该对象, 当所绑定的数据有变更时, 通过dep.notify()通知Watcher。
  3. Compile是HTML指令解析器,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。
  4. Watcher是连接Observer和Compile的桥梁,Compile解析指令时会创建一个对应的Watcher并绑定update方法 , 添加到Dep对象上。

Vue源码解读之Dep,Observer和Watcher

接下来我们来分析一下对象具体的代码实现。

Observer


var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
// 给value添加__ob__属性,值就是本Observer对象,value.__ob__ = this;
// Vue.$data 中每个对象都 __ob__ 属性,包括 Vue.$data对象本身
def(value, '__ob__', this);
//判断是否为数组,不是的话调用walk()添加getter和setter
//如果是数组,调用observeArray()遍历数组,为数组内每个对象添加getter和setter
if (Array.isArray(value)) {
var augment = hasProto
? protoAugment
: copyAugment;
augment(value, arrayMethods, arrayKeys);
this.observeArray(value);
} else {
this.walk(value);
}
};

walk和defineReactive


// 遍历每个属性并将它们转换为getter/setter。只有当值类型为对象时才调用此方法。
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
}; function defineReactive (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
//获取已经实现的 getter /setter 方法
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
} var childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
//Dep.target 全局变量指向的就是当前正在解析指令的Complie生成的 Watcher
// 会执行到 dep.addSub(Dep.target), 将 Watcher 添加到 Dep 对象的 Watcher 列表中
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if ("development" !== 'production' && customSetter) {
customSetter();
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();//如果数据被重新赋值了, 调用 Dep 的 notify 方法, 通知所有的 Watcher
}
});
}

observeArray和observe


Observer.prototype.observeArray = function observeArray (items) {
for (var i = 0, l = items.length; i < l; i++) {
// 如果是数组继续执行 observe 方法, 其中会继续新建 Observer 对象, 直到穷举完毕执行 walk 方法
observe(items[i]);
}
}; function observe (value, asRootData) {
if (!isObject(value) || value instanceof VNode) {
return
}
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob
}

Dep


// Dep是订阅者Watcher对应的数据依赖
var Dep = function Dep () {
//每个Dep都有唯一的ID
this.id = uid++;
//subs用于存放依赖
this.subs = [];
}; //向subs数组添加依赖
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
//移除依赖
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub);
};
//设置某个Watcher的依赖
//这里添加了Dep.target是否存在的判断,目的是判断是不是Watcher的构造函数调用
//也就是说判断他是Watcher的this.get调用的,而不是普通调用
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
}; Dep.prototype.notify = function notify () {
var subs = this.subs.slice();
//通知所有绑定 Watcher。调用watcher的update()
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};

Watcher

在initMixin()初始化完成Vue实例所有的配置之后,在最后根据el是否存在,调用$mount()实现挂载。


if (vm.$options.el) {
vm.$mount(vm.$options.el);
}

$mount


//这是供外部使用的公共的方法
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};

mountComponent


function mountComponent (
vm,
el,
hydrating
) {
vm.$el = el;
//这个if判断目的在检测vm.$options.render
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode;
{
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el || el) {
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
);
} else {
warn(
'Failed to mount component: template or render function not defined.',
vm
);
}
}
}
// 调用钩子函数
callHook(vm, 'beforeMount'); // 定义updateComponent,将作为Watcher对象的参数传入。
var updateComponent;
/* istanbul ignore if */
if ("development" !== 'production' && config.performance && mark) {
updateComponent = function () {
var name = vm._name;
var id = vm._uid;
var startTag = "vue-perf-start:" + id;
var endTag = "vue-perf-end:" + id; mark(startTag);
var vnode = vm._render();
mark(endTag);
measure(("vue " + name + " render"), startTag, endTag); mark(startTag);
vm._update(vnode, hydrating);
mark(endTag);
measure(("vue " + name + " patch"), startTag, endTag);
};
} else {
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
} new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
hydrating = false; // 调用钩子
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
return vm
}

watcher

mountComponent在构造新的Watcher对象传了当前vue实例、updateComponent函数、空函数这三个参数。


var Watcher = function Watcher (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
this.vm = vm;
if (isRenderWatcher) {
vm._watcher = this;
}
// 当前Watcher添加到vue实例上
vm._watchers.push(this);
// 参数配置,options默认false
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;
}
this.cb = cb;
this.id = ++uid$1; // uid for batching
this.active = true;
this.dirty = this.computed; //用于计算属性
this.deps = [];
this.newDeps = [];
//内容不可重复的数组对象
this.depIds = new _Set();
this.newDepIds = new _Set();
this.expression = expOrFn.toString();
//将watcher对象的getter设为updateComponent方法
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = function () {};
"development" !== 'production' && warn(
"Failed watching path: \"" + expOrFn + "\" " +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
);
}
}
//如果是计算属性,就创建Dep数据依赖,否则通过get获取value
if (this.computed) {
this.value = undefined;
this.dep = new Dep();
} else {
this.value = this.get();
}
};
};

get


Watcher.prototype.get = function get () {
pushTarget(this);//将Dep的target添加到targetStack,同时Dep的target赋值为当前watcher对象
var value;
var vm = this.vm;
try {
// 调用updateComponent方法
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) {
traverse(value);
}
popTarget();//update执行完成后,又将Dep.target从targetStack弹出。
this.cleanupDeps();
}
return value
}; //这是全局唯一的,因为任何时候都可能只有一个watcher正在评估。
Dep.target = null;
var targetStack = []; function pushTarget (_target) {
if (Dep.target) { targetStack.push(Dep.target); }
Dep.target = _target;
} function popTarget () {
Dep.target = targetStack.pop();
}

Watcher的get方法实际上就是调用了updateComponent方法,updateComponent就是


updateComponent = function() {
vm._update(vm._render(), hydrating);
};

调用这个函数会接着调用_update函数更新dom,这个是挂载到vue原型的方法,而_render方法重新渲染了vnode。


Vue.prototype._render = function () {
var vm = this;
var ref = vm.$options;
var render = ref.render;
var _parentVnode = ref._parentVnode;
// reset _rendered flag on slots for duplicate slot check
{
for (var key in vm.$slots) {
// $flow-disable-line
vm.$slots[key]._rendered = false;
}
}
if (_parentVnode) {
vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject;
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode;
// render self
var vnode;
try {
vnode = render.call(vm._renderProxy, vm.$createElement);
} catch (e) {
handleError(e, vm, "render");
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
{
if (vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);
} catch (e) {
handleError(e, vm, "renderError");
vnode = vm._vnode;
}
} else {
vnode = vm._vnode;
}
}
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
if ("development" !== 'production' && Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
);
}
vnode = createEmptyVNode();
}
// set parent
vnode.parent = _parentVnode;
return vnode
};

update


//当依赖项改变时调用。前面有提到。
Watcher.prototype.update = function update () {
var this$1 = this; /* istanbul ignore else */
//是否计算属性
if (this.computed) {
if (this.dep.subs.length === 0) {
this.dirty = true;
} else {
this.getAndInvoke(function () {
this$1.dep.notify();
});
}
//是否缓存
} else if (this.sync) {
//调用run方法执行回调函数
this.run();
} else {
queueWatcher(this);
}
}; Watcher.prototype.run = function run () {
if (this.active) {
//这里的cb就是指watcher的回调函数
this.getAndInvoke(this.cb);
}
}; Watcher.prototype.getAndInvoke = function getAndInvoke (cb) {
var value = this.get();
if (value !== this.value ||isObject(value) ||this.deep) {
//设置新的值
var oldValue = this.value;
this.value = value;
this.dirty = false;
if (this.user) {
try {
cb.call(this.vm, value, oldValue);
} catch (e) {
handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
}
} else {
//执行回调函数
cb.call(this.vm, value, oldValue);
}
}
};

后记

关于Vue数据双向绑定的文章很多,查看越多资料越觉得自己只是浅薄,要更努力才行啊。
学习到比较系统思路的来自于:https://segmentfault.com/a/11...
十分感谢

来源:https://segmentfault.com/a/1190000016208088