3.2 挂载的基本思路
vue挂载的流程是比较复杂的,我们通过流程图理清基本的实现思路。
如果用一句话概括挂载的过程,可以描述为挂载组件,将渲染函数生成虚拟DOM,更新视图时,将虚拟DOM渲染成为真正的DOM。
详细的过程是:首先确定挂载的DOM元素,且必须保证该元素不能为html,body
这类跟节点。判断选项中是否有render
这个属性(如果不在运行时编译,则在选项初始化时需要传递render
渲染函数)。当有render
这个属性时,默认我们使用的是runtime-only
的版本,从而跳过模板编译阶段,调用真正的挂载函数$mount
。另一方面,当我们传递是template
模板时(即在不使用外置编译器的情况下,我们将使用runtime+compile
的版本),Vue源码将首先进入编译阶段。该阶段的核心是两步,一个是把模板解析成抽象的语法树,也就是我们常听到的AST
,第二个是根据给定的AST生成目标平台所需的代码,在浏览器端是前面提到的render
函数。完成模板编译后,同样会进入$mount
挂载阶段。真正的挂载过程,执行的是mountComponent
方法,该函数的核心是实例化一个渲染watcher
,具体watcher
的内容,另外放章节讨论。我们只要知道渲染watcher
的作用,一个是初始化的时候会执行回调函数,另一个是当 vm 实例中监测的数据发生变化的时候执行回调函数。而这个回调函数就是updateComponent
,这个方法会通过vm._render
生成虚拟DOM
,并最终通过vm._update
将虚拟DOM
转化为真正的DOM
。
往下,我们从代码的角度出发,了解一下挂载的实现思路,下面只提取mount骨架代码说明。
// 内部真正实现挂载的方法
Vue.prototype.$mount = function (el, hydrating) {
el = el && inBrowser ? query(el) : undefined;
// 调用mountComponent方法挂载
return mountComponent(this, el, hydrating)
};
// 缓存了原型上的 $mount 方法
var mount = Vue.prototype.$mount;
// 重新定义$mount,为包含编译器和不包含编译器的版本提供不同封装,最终调用的是缓存原型上的$mount方法
Vue.prototype.$mount = function (el, hydrating) {
// 获取挂载元素
el = el && query(el);
// 挂载元素不能为跟节点
if (el === document.body || el === document.documentElement) {
warn(
"Do not mount Vue to <html> or <body> - mount to normal elements instead."
);
return this
}
var options = this.$options;
// 需要编译 or 不需要编译
if (!options.render) {
···
// 使用内部编译器编译模板
}
// 最终调用缓存的$mount方法
return mount.call(this, el, hydrating)
}
// mountComponent方法思路
function mountComponent(vm, el, hydrating) {
// 定义updateComponent方法,在watch回调时调用。
updateComponent = function () {
// render函数渲染成虚拟DOM, 虚拟DOM渲染成真实的DOM
vm._update(vm._render(), hydrating);
};
// 实例化渲染watcher
new Watcher(vm, updateComponent, noop, {})
}