3.2 挂载的基本思路

vue挂载的流程是比较复杂的,我们通过流程图理清基本的实现思路。

3.2 挂载的基本思路 - 图1

如果用一句话概括挂载的过程,可以描述为挂载组件,将渲染函数生成虚拟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骨架代码说明。

  1. // 内部真正实现挂载的方法
  2. Vue.prototype.$mount = function (el, hydrating) {
  3. el = el && inBrowser ? query(el) : undefined;
  4. // 调用mountComponent方法挂载
  5. return mountComponent(this, el, hydrating)
  6. };
  7. // 缓存了原型上的 $mount 方法
  8. var mount = Vue.prototype.$mount;
  9. // 重新定义$mount,为包含编译器和不包含编译器的版本提供不同封装,最终调用的是缓存原型上的$mount方法
  10. Vue.prototype.$mount = function (el, hydrating) {
  11. // 获取挂载元素
  12. el = el && query(el);
  13. // 挂载元素不能为跟节点
  14. if (el === document.body || el === document.documentElement) {
  15. warn(
  16. "Do not mount Vue to <html> or <body> - mount to normal elements instead."
  17. );
  18. return this
  19. }
  20. var options = this.$options;
  21. // 需要编译 or 不需要编译
  22. if (!options.render) {
  23. ···
  24. // 使用内部编译器编译模板
  25. }
  26. // 最终调用缓存的$mount方法
  27. return mount.call(this, el, hydrating)
  28. }
  29. // mountComponent方法思路
  30. function mountComponent(vm, el, hydrating) {
  31. // 定义updateComponent方法,在watch回调时调用。
  32. updateComponent = function () {
  33. // render函数渲染成虚拟DOM, 虚拟DOM渲染成真实的DOM
  34. vm._update(vm._render(), hydrating);
  35. };
  36. // 实例化渲染watcher
  37. new Watcher(vm, updateComponent, noop, {})
  38. }