大致流程
- 发生在beforeCreate和created之间initState(vm)中的defineProperty
- 发生在beforeMount和mounted之间的Dep和Watcher的初始化
- 发生在beforeUpdate前到updated触发,这期间Watcher的相关变化
第一步:数据初始化
在new一个Vue实例时,其实只执行了一个this._init(options)
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
在_init方法中,包含以下这些操作,其中initState包含对data和props的处理过程;initData包含了对data创建观察者的observe函数
Vue.prototype._init = function (options) {
...
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm);
initState(vm);
initProvide(vm);
callHook(vm, 'created');
...
}
function initState (vm) {
...
if (opts.data) {
initData(vm);
} else {
observe(vm._data = {}, true);
...
}
function initData (vm) {
var data = vm.$options.data;
data = vm._data = typeof data === 'function' // 这行代码解释了平时为啥data为什么支持函数式的
? getData(data, vm)
: data || {};
...
proxy(vm, "_data", key);// 将data绑定到vue的this上
...
observe(data, true);
}
这里observe(data)会return一个Observe类的实例
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
}
Observe类将传进来的参数进行递归调用,最终都会调用this.walk
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this);
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
};
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]);
}
};
终于看见和definePropoty长得差不多的defineReactive,其实defineReactive就是创建响应式对象,是对definePropoty的一层封装,到这里响应式数据的初始化就算完成了,完整代码如下:
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
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;
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;
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter();
}
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}
第二步:建立依赖
建立依赖其实就是触发Object.defineProperty中定义get的一个过程,我们都知道get是在获取对象值的时候触发的函数,在vue运行过程中,get的触发是在beforeMount和mounted这两个声明周期之间,这里就不去罗列模板解析过程了,大致就是一个template => AST => render函数 => Vnode => DOM的过程,这里接着最上面created声明周期后的部分进行,执行了$mount
Vue.prototype._init = function (options) {
initState(vm);
initProvide(vm);
callHook(vm, 'created');
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
}
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};
function mountComponent (
vm,
el,
hydrating
) {
vm.$el = el;
...
callHook(vm, 'beforeMount');
...
// 这里有一段updateComponent的定义
...
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true );
...
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
return vm
}
从上图可以看出beforeMount和mounted之间其实就定义了一个名为updateComponent(它是Watcher里的一个回调,发生在Watcher的get中),然后new了一个Watcher。
这里主要讲讲Dep和Watcher,先介绍Watcher和Watcher是如何作为target定义到Dep上的:
var Watcher = function Watcher (
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
this.vm = vm;
if (isRenderWatcher) {
vm._watcher = this;
}
vm._watchers.push(this);
if (options) {
// mounted阶段new的那个Watcher里只有before字段,其他初始化全都是false
this.deep = !!options.deep;
this.user = !!options.user;
this.lazy = !!options.lazy;
this.sync = !!options.sync;
this.before = options.before;
} else {
this.deep = this.user = this.lazy = this.sync = false;
}
this.cb = cb;
this.id = ++uid$2; // uid for batching
this.active = true;
this.dirty = this.lazy; // for lazy watchers
this.deps = [];
this.newDeps = [];
this.depIds = new _Set();
this.newDepIds = new _Set();
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: '';
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = noop;
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
);
}
}
this.value = this.lazy
? undefined
: this.get();
};
上面的最后调用了this.get(),在get()函数里利用pushTarget把Watcher 作为 target 定义在了Dep上,并且执行了this.getter.call(vm, vm);这里的getter是Watcher构造函数的第二个参数expOrFn,内容为vm._update(vm._render(), hydrating),也就是触发了页面的渲染
function pushTarget (target) {
targetStack.push(target);
Dep.target = target;
}
Watcher.prototype.get = function get () {
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
} else {
throw e
}
} finally {
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
};
在vm._update的渲染过程中,因为引用了data中的数据,所以会触发第一阶段中defineProperty为data内数据设置的get函数,代码如下:如果Dep.target存在会调用dep.depend()(Dep.target其实是一个Watcher)
function defineReactive$$1 (){
Object.defineProperty(obj, key, {
...
var dep = new Dep();
...
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
...
}
}
}
}
再看看Dep类,在defineReactive中会new一个Dep的实例,这里subs是一个装watcher的数组,一般在不自定义watch的前提下,这个数组里都只有一个Watcher
var Dep = function Dep () {
this.id = uid++;
this.subs = [];
};
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this); // 根据上面的描述这里Dep.target就是Watcher
}
};
Watcher.prototype.addDep = function addDep (dep) {
var id = dep.id;
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
dep.addSub(this);
}
}
};
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
忽视掉和响应式数据无关的部分,到这里基本就是mount结束的地方了,总结下都干了什么,触发beforeMount生命周期,new了一个Watcher对象,渲染模板,触发数据的get初始化,对每个响应式数据的Dep实例进行依赖收集,然后触发Mounted生命周期。
第三步:派发更新
当有响应式的数据被改变时,触发set函数,调用dep.notify()
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter();
}
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
这里subs就是一个装Watcher的数组(在没有绑定自定义Watcher的简单的Vue对象中,这个数组的长度是1),所以就等于是调用了当前vue对象对应Watcher的update()
Dep.prototype.notify = function notify () {
var subs = this.subs.slice();
if (process.env.NODE_ENV !== 'production' && !config.async) {
subs.sort(function (a, b) { return a.id - b.id; });
}
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
Watcher的update()会对根据Watcher初始化传入的option中sync字段进行一个判断,如果是true的直接触发run(),如果不是会进行一个队列的操作。因为我们在$mount过程中new Watcher时传的option只有before字段,所以其他lazy,sync等字段都是false,所以这里会产生一个队列,用于存放Watcher
Watcher.prototype.update = function update () {
if (this.lazy) { //这里是false
this.dirty = true;
} else if (this.sync) { // 这里是false
this.run();
} else {
queueWatcher(this);
}
};
这个队列会先判断之前是否添加过这个watcher,如果没有则添加,并会有一个针对id的排序插入
function queueWatcher (watcher) {
var id = watcher.id;
if (has[id] == null) {
has[id] = true;
if (!flushing) {
queue.push(watcher);
} else {
var i = queue.length - 1;
while (i > index && queue[i].id > watcher.id) {
i--;
}
queue.splice(i + 1, 0, watcher);
}
if (!waiting) {
waiting = true;
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue();
return
}
nextTick(flushSchedulerQueue);
}
}
}
flushSchedulerQueue,首先会对队列中的Watcher进行排序,然后触发option中的before,也就是beforeUpdate的生命周期函数,然后执行Watcher.run()
function flushSchedulerQueue () {
queue.sort(function (a, b) { return a.id - b.id; });
...
for (index = 0; index < queue.length; index++) {
watcher = queue[index];
if (watcher.before) {
watcher.before();
}
/**
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
**/
id = watcher.id;
has[id] = null;
watcher.run();
}
...
// 下面就不介绍了。。。
// 队列的备份
var activatedQueue = activatedChildren.slice();
var updatedQueue = queue.slice();
// 队列的初始化
resetSchedulerState();
// 触发activated和updated的生命周期函数
callActivatedHooks(activatedQueue);
callUpdatedHooks(updatedQueue);
}
run的时候触发get(),会和首次mount过程类似,多了patch的过程,其中涉及著名的Diff算法,用于渲染页面,从而更新页面,并建立新的依赖关系
Watcher.prototype.run = function run () {
if (this.active) {
var value = this.get();
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
var oldValue = this.value;
this.value = value;
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue);
} catch (e) {
handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
}
} else {
this.cb.call(this.vm, value, oldValue);
}
}
}
};
完~
大致流程就是这样了,似乎写的有点乱,如有问题欢迎大佬们指正
网友评论