vue源码学习-实例挂载的实现
Vue实例挂载的实现
Vue中我们是通过$mount实例方法去挂载vm的,$mount方法在多个文件中都有定义,如src/platform/web/entry-runtime-with-compiler.js、src/platform/web/runtime/index.js、src/platform/weex/runtime/index.js。因为$mount这个方法的实现是和平台、构建方式都相关的。接下来我们重点分析带compiler版本的$mount实现,因为抛开webpack的vue-loader,我们在纯前端浏览器环境分析Vue的工作原理,有助于我们对原理的理解深入。
如果用一句话概括挂载的过程,可以描述为挂载组件,将渲染函数生成虚拟DOM,更新视图的时候,将虚拟DOM渲染成为真正的DOM
详细的过程是,首先确定挂载的DOM元素,且必须保证该元素不能为html,body这类根节点,判断选项中是否有render这个属性(如果不在运行编译时候,则在选项时,需要传递render渲染函数)。当有render这个属性时,默认我们使用runtime-only版本,从而跳过模板编译阶段,调用真正挂载函数$mount。另一方面,当我们传递是templa模板时(即在不使用外置编译器的情况下,我们将使用runtime+compiler的版本),Vue源码将首先进入编译阶段。该阶段的核心是两步,一个是把模板解析成抽象语法树,也就四我们常听到的AST,第二个是根据给定的AST生成目标平台所需要的代码,在浏览器端是前面提到的render函数。完成模板编译后,同样会进入$mount挂载阶段。真正挂载的过程,执行的是mountComponent方法,改函数的核心是实例化一个渲染watcher,具体watcher的内容,这里先不说明。我们只要知道渲染watcher的作用,一个初始化的时候会执行回调函数,另一个是当vm实例中检测的数据发生变化的时候执行回调函数。而这个回调函数就是updateComponent,这个方法会通过vm._render生成虚拟DOM,并最终通过vm.update将虚拟DOM转化为真正的DOM。
往下,我们从代码的角度出发,了解一下挂载的实现思路,下面值提取mount骨架代码说明。
// 内部真正实现挂载的方法
Vue.prototype.$mount = funtion (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) {
"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, {})
}