前言
本文是vue2.x源码分析的第五篇,主要讲解vue实例的观察者收集、组件渲染挂载以及页面更新过程!
先看调用形式
vm.$mount(vm.$options.el);
1、分析 $mount
Vue$3.prototype.$mount = function (el,hydrating) {
el = el && query(el);
//el不能是html和body元素
if (el === document.body || el === document.documentElement) {
"development" !== 'production' && warn(
"Do not mount Vue to or - mount to normal elements instead."
);
return this
}
var options = this.$options;
// 如果没有提供render函数,尝试用template,若没有提供template,则取el的outerHTML作为template
if (!options.render) {
var template = options.template;
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template);
/* istanbul ignore if */
if ("development" !== 'production' && !template) {
warn(
("Template element not found or is empty: " + (options.template)),
this
);
}
}
} else if (template.nodeType) {
template = template.innerHTML;
} else {
{
warn('invalid template option:' + template, this);
}
return this
}
} else if (el) {
template = getOuterHTML(el);
}
if (template) {
/* istanbul ignore if */
if ("development" !== 'production' && config.performance && mark) {
mark('compile');
}
//对template进行编译,主要是用正则对template进行解析
var ref = compileToFunctions(template, { //主要函数1
shouldDecodeNewlines: shouldDecodeNewlines,
delimiters: options.delimiters
}, this);
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
options.render = render;
options.staticRenderFns = staticRenderFns;
if ("development" !== 'production' && config.performance && mark) {
mark('compile end');
measure(((this._name) + " compile"), 'compile', 'compile end');
}
}
}
return mount.call(this, el, hydrating) //主要函数2
};
2、分析 compileToFunctions
/*
该函数最终返回一个对象,结构如下:
{
render:function(){...},
staticRenderFns:Array()
}
*/
function compileToFunctions (template,options,vm) {
options = options || {};
{
//这里省略了CSP内容安全策略检查相关代码
...
//若该template之前被编译过,直接返回
var key = options.delimiters
? String(options.delimiters) + template
: template;
if (functionCompileCache[key]) {
return functionCompileCache[key]
}
// 开始编译
var compiled = compile(template, options); //返回的结果结构如下,下篇详细分析
/*
{
ast:Object, //ast包含了template的所有信息:tag,data,children等
render:'with(this){return _c...}',
staticRenderFns:Array(0),
errors:Array(0),
tips:Array(0),
__proto__:Object
}
*/
}
// 编译错误检查
{
if (compiled.errors && compiled.errors.length) {
warn(
"Error compiling template:\n\n" + template + "\n\n" +
compiled.errors.map(function (e) { return ("- " + e); }).join('\n') + '\n',
vm
);
}
if (compiled.tips && compiled.tips.length) {
compiled.tips.forEach(function (msg) { return tip(msg, vm); });
}
}
// compiled.render目前还只是string类型,以下将其变为function。staticRenderFns同理
var res = {};
var fnGenErrors = [];
//将string类型的函数变为真正的函数.实现方式:new Function(compiled.render)
res.render = makeFunction(compiled.render, fnGenErrors);
var l = compiled.staticRenderFns.length;
res.staticRenderFns = new Array(l);
for (var i = 0; i < l; i++) {
res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors);
}
// 生成函数过程的错误检查
{
if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
warn(
"Failed to generate render function:\n\n" +
fnGenErrors.map(function (ref) {
var err = ref.err;
var code = ref.code;
return ((err.toString()) + " in\n\n" + code + "\n");
}).join('\n'),
vm
);
}
}
//将最终结果缓存在functionCompileCache,避免同样的template再次被编译
return (functionCompileCache[key] = res)
}
3、分析 mount.call(this, el, hydrating)
Vue$3.prototype.$mount = function (el,hydrating) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating) //主要函数
};
来看看 mountComponent(this, el, hydrating)
function mountComponent (vm,el,hydrating) {
vm.$el = el;
//当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
);
}
}
}
//生命周期函数beforeMount被调用
callHook(vm, 'beforeMount');
var updateComponent;
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((name + " render"), startTag, endTag);
mark(startTag);
vm._update(vnode, hydrating);
mark(endTag);
measure((name + " patch"), startTag, endTag);
};
} else {
updateComponent = function () { //对updateComponent进行定义
vm._update(vm._render(), hydrating);
};
}
vm._watcher = new Watcher(vm, updateComponent, noop); //对updateComponent建一个watcher,这是最重要的一个watcher,负责页面的渲染和更新,单独保存在vm._watcher上,也会保存在vm._watchers数组中
hydrating = false;
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
//生命周期函数mounted被调用
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, 'mounted');
}
return vm
}
又要进入Watcher了
var Watcher = function Watcher (vm,expOrFn,cb,options) {
this.vm = vm;
vm._watchers.push(this);
// ... 略过属性的处理
this.expression = expOrFn.toString();
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn; //updateComponent被赋值给getter
} else {
this.getter = parsePath(expOrFn);
//略过错误处理
}
this.value = this.lazy //这里lazy=false,故执行get函数
? undefined
: this.get();
};
来看看get
Watcher.prototype.get = function get () {
pushTarget(this); //将Dep.target设为this
var value;
var vm = this.vm;
if (this.user) {
try {
value = this.getter.call(vm, vm);
} catch (e) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
}
} else {
value = this.getter.call(vm, vm); //getter执行,也即updateComponent执行
}
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
return value
};
updateComponent得到了执行,也即
vm._update(vm._render(), hydrating);
得到执行,这会先执行vm._render,再执行vm._update
先看看vm._render
/*
该函数最终返回一个对象vnode,结构如下(只列出了最重要的几个属性):
{
tag:'',
data:Object,
children:Array(),
elm:
}
这期间会对vm上的属性进行读取操作,故会触发属性的get函数,get函数里就会进行属性的依赖收集
*/
Vue.prototype._render = function () {
var vm = this;
var ref = vm.$options;
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
var _parentVnode = ref._parentVnode;
if (vm._isMounted) {
// Unkonwn5.1
for (var key in vm.$slots) {
vm.$slots[key] = cloneVNodes(vm.$slots[key]);
}
}
vm.$scopedSlots = (_parentVnode && _parentVnode.data.scopedSlots) || emptyObject;
if (staticRenderFns && !vm._staticTrees) {
vm._staticTrees = [];
}
// 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) {
//略
}
}
// 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
};
再看看vm._update
/*主要是调用vm.__patch__方法完成最终渲染,期间运用了虚拟DOM算法*/
Vue.prototype._update = function (vnode, hydrating) {
var vm = this;
if (vm._isMounted) {
callHook(vm, 'beforeUpdate');
}
var prevEl = vm.$el;
var prevVnode = vm._vnode;
var prevActiveInstance = activeInstance;
activeInstance = vm;
vm._vnode = vnode;
if (!prevVnode) {
// 初次渲染,vm__patch__执行完毕后vm.$el才得到DOM结构
vm.$el = vm.__patch__(
vm.$el, vnode, hydrating, false /* removeOnly */,
vm.$options._parentElm,
vm.$options._refElm
);
} else {
// 二次渲染,即更新操作
vm.$el = vm.__patch__(prevVnode, vnode);
}
activeInstance = prevActiveInstance;
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null;
}
if (vm.$el) {
vm.$el.__vue__ = vm;
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el;
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
};
patch的过程会用到原始的DOM API(见下面的nodeOps),期间包含虚拟DOM算法,后面单独分析。
到这里页面的第一次渲染就完成了!
var nodeOps = Object.freeze({
createElement: createElement$1,
createElementNS: createElementNS,
createTextNode: createTextNode,
createComment: createComment,
insertBefore: insertBefore,
removeChild: removeChild,
appendChild: appendChild,
parentNode: parentNode,
nextSibling: nextSibling,
tagName: tagName,
setTextContent: setTextContent,
setAttribute: setAttribute
});
接着vm._render来看属性的依赖收集是怎么回事,注意调用时传入的是vm._renderProxy,而不是vm实例。vm._renderProxy有个has代理,即访问vm上的属性时,会先调用has函数
render.call(vm._renderProxy, vm.$createElement);
render完全是由字符串拼接而成的函数,长这个样子
(function() {
with(this){return _c('div',{attrs:{"id":"app"}},[_v("\n\t\t"+_s(message)+"\n\t\t"),
_c('div',{domProps:{"textContent":_s(abc)}})])}
})
这里的this即vm._renderProxy,由于使用了with,故函数的当前活动对象就是vm._renderProxy对象,所以内部不需要
这样取属性:vm._renderProxy.xx,直接用xx就可拿到。
这里的_c,_v,_s函数在初始化过程中已定义好,如下:
vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); }; //返回VNODE
Vue.prototype._s = _toString; //转成字符串
Vue.prototype._v = createTextVNode; //返回VNODE
function createTextVNode (val) {
return new VNode(undefined, undefined, undefined, String(val))
}
with
大家可能用的少,故分析下render函数的执行过程:
- 判断第一个_c在不在vm上;
- 判断_v在不在vm上;
- 判断第一个_s在不在vm上;
- 判断message在不在vm上;
- 触发message的get函数;
- 调用_s(vm.message);
- 调用_v,生成text类型的VNODE;
- 判断第二个_c在不在vm上;
- 判断第二个_s在不在vm上;
- 判断abc在不在vm上;
- 触发abc的get函数;
- 调用_s(vm.abc);
- 调用第二个_c,生成VNODE;
- 调用第一个_c,生成VNODE,执行完毕
来看看第5和11中如何进行依赖收集(以5为例,11同理)
- 首先触发vm.message的get函数,如下:
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]//这里sourceKey就是_data,就是取vm._data.message
};
接着触发vm._data中message的get函数,这个get是通过defineReactive$$1定义的,defineReactive$$1在第四课也有分析,如下:
function defineReactive$$1 (obj,key,val,customSetter) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// 引用预先定义的getter/setters
var getter = property && property.get;
var setter = property && property.set;
//val可能是对象,故继续观测
var childOb = observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () { //本节中就是触发这个get函数
var value = getter ? getter.call(obj) : val;
if (Dep.target) { //这里的Dep.target在3中分析mount.call时设为了最主要的那个watch实例
dep.depend();//每个属性都会对应一个dep实例,用于收集该属性的观察者,这里就将最重要的watcher实例进行了收集
//子对象也必须收集父对象的观察者,否则子对象里的属性改变后无法更新,这个最重要的观察者必须被每个属性所收集
if (childOb) {
childOb.dep.depend(); //
}
//对数组的依赖处理
if (Array.isArray(value)) {
dependArray(value);
}
}
return value
},
//set省略
});
现在假如message属性发生了变化,来看看页面是如何更新的
setTimeout(()=>vm.message='world',0)
第一步当然是触发vm.message的set函数
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val; //这里sourceKey就是_data,就是设置vm._data.message=val
};
这又会触发vm.data中message的set函数
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if ("development" !== 'production' && customSetter) {
customSetter();//报错用
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = observe(newVal); //设置新值后,对新值进行观测
dep.notify(); //触发观察者的回调或get函数
}
显然这里主要是执行dep.notify(),当然不能忘记对新值进行观测
Dep.prototype.notify = function notify () {
//之前message的观察者就是收集在this.subs数组里
var subs = this.subs.slice();
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update(); //对message的每个观察者分别调用update,不要忘记观察者就是watcher实例,它保存了回调函数cb,expOrFun等信息
}
};
来看看update
Watcher.prototype.update = function update () {
if (this.lazy) { //本节中lazy=false
this.dirty = true;
} else if (this.sync) { //本节中不是同步watcher
this.run();
} else {
queueWatcher(this); //本节执行该函数
}
};
来看看queueWatcher
function queueWatcher (watcher) {
var id = watcher.id;
if (has[id] == null) {
has[id] = true;
if (!flushing) { //全局变量,默认false
queue.push(watcher); //将watcher放入queue数组
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
var i = queue.length - 1;
while (i >= 0 && queue[i].id > watcher.id) {
i--;
}
queue.splice(Math.max(i, index) + 1, 0, watcher);
}
// queue the flush
if (!waiting) { //全局变量,默认false
waiting = true;
nextTick(flushSchedulerQueue); //这里的nextTick是作者自己实现的,属于异步微任务,跟setTimeout等函数类似
//这样设计的目的是不让当前watcher马上执行,而是等某个属性的所有watcher都进入queue后再一起执行,在本节里
//例子中,message属性上有三个watcher,分别对应是watch选项、最重要watcher、computed选项,而由于computed选项中
//的watcher的lazy=true,故不会进入到queue.最终只有两个watcher进入queue
}
}
}
接下来就是等宏任务执行完,然后flushSchedulerQueue开始执行
function flushSchedulerQueue () {
flushing = true;
var watcher, id, vm;
// 对queue进行排序
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child)
// 2. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
// 先创建的watcher排在前面
queue.sort(function (a, b) { return a.id - b.id; });
//不对length进行缓存,因为在执行过程中可能有新watcher加入
for (index = 0; index < queue.length; index++) {
watcher = queue[index];
id = watcher.id;
has[id] = null;
watcher.run();//对每个watcher分别调用其run函数
// in dev build, check and stop circular updates.
if ("development" !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1;
if (circular[id] > config._maxUpdateCount) {
warn(
'You may have an infinite update loop ' + (
watcher.user
? ("in watcher with expression \"" + (watcher.expression) + "\"")
: "in a component render function."
),
watcher.vm
);
break
}
}
}
// 调用生命周期函数updated之前重置scheduler
var oldQueue = queue.slice();
resetSchedulerState();
// call updated hooks
index = oldQueue.length;
while (index--) {
watcher = oldQueue[index];
vm = watcher.vm;
if (vm._watcher === watcher && vm._isMounted) {
callHook(vm, 'updated'); //调用生命周期函数updated
}
}
// devtool hook
if (devtools && config.devtools) {
devtools.emit('flush');
}
}
来看看run函数
Watcher.prototype.run = function run () {
if (this.active) {
var value = this.get(); //watch选项中定义的用户watcher会执行,取得新设的值'world';最重要的render watcher也执行这个get.不过user watcher执行时是取值操作,而render watcher执行时执行了updateComponent,就是
//这个:
/*updateComponent = function () {
vm._update(vm._render(), hydrating); //这里就回到了初次渲染的过程,于是完成了更新
};*/
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) { //watch选项中定义的watcher属于user=true
try {
this.cb.call(this.vm, value, oldValue); //调用watch选项中对message设置的回调函数cb,输出'message changed'
} catch (e) {
handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
}
} else {
this.cb.call(this.vm, value, oldValue);
}
}
}
};