读Vue2源码(1)-init

76 阅读2分钟

一直都想看看 vue 源码,但是不知道从何下手,感觉是一项很难很复杂的工作。 我选择的是写一段简单的 vue 代码,然后通过 debugger 的手段一步一步看看 vue 做了些什么,此外还同步看了 Vue.js 技术揭秘 这本书进行理解。

准备

先去官网看看 vue 是怎么入门的,复制这份代码创建一个 html

<body>
  <div id="app">
    {{ message }}
  </div>
</body>
<script src="vue.js"></script>
<script>
  var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})
</script>

这里 vue.js 就是从官网的引入地址 cdn.jsdelivr.net/npm/vue@2/d… 中粘贴复制出来的,便于调试。

这边有一万行代码,没必要从头开始看,也不必知道每一步做了什么,就按照我们在第二个 script 标签中写的内容,看看 vue 是怎么做的。

new Vue

让我们 vue.js 中找到 Vue 这个方法,在第一行打个 debugger,看看 new Vue 时做了什么。 image.png

  1. 判断 Vue 方法是否是通过 new 方法调用的,不是就提示错误。
  2. 进入初始化 _init() 方法,传入我们调用 Vue 时传入的参数,也就是:
{
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
}

_init() 方法

找到 _init() 方法可以发现,他是定义在 Vue 函数的原型上的,他做了很多初始化的操作,但是先让我们忽略很多操作(...表示暂时不看的代码),找到在 _init 的最后,调用了 vm.$mount(vm.$options.el) 实现挂载。

      Vue.prototype._init = function (options) {
          var vm = this;
          ...
          if (vm.$options.el) {             // vm.$options.el 为 '#app'
              vm.$mount(vm.$options.el);
          }
      };

$mount 挂载

1. 缓存原型上的 $mount 方法

首先缓存了原型上以前的 $mount 方法,并定义新的 $mount 方法。

 Vue.prototype.$mount = function (el, hydrating) {
      el = el && inBrowser ? query(el) : undefined;
      return mountComponent(this, el, hydrating);
  };
 var mount = Vue.prototype.$mount;    // mount 表示原型上的 $mount 方法
Vue.prototype.$mount = function (el, hydrating) { ... }

2. Vue 不能挂载在 bodyhtml 这样的根节点上

在新的 $mount 方法中,使用 query 方法找到的是 #app 这个 dom 节点,判断 #app 不能挂载在 bodyhtml 这样的根节点上。

el = el && query(el);
if (el === document.body || el === document.documentElement) {
    warn$2("Do not mount Vue to <html> or <body> - mount to normal elements instead.");
    return this;
}

3. 如果没有定义 render 方法,则会把 el 或者 template 字符串转换成 render 方法

  1. 先对 template 进行一些校验。
  2. 如果没有 template ,将 el 通过 template = getOuterHTML(el) 转换为 template。此段代码中,转换之后的 template 为:
"<div id="app"> {{ message }} </div>"
  1. template 通过 compileToFunctions 方法转换为 render
var _a = compileToFunctions(template, { ... , this), render = _a.render, staticRenderFns = _a.staticRenderFns;
options.render = render;

这里我们要牢记,在 Vue 2.0 版本中,所有 Vue 的组件的渲染最终都需要 render 方法,无论我们是用单文件 .vue 方式开发组件,还是写了 el 或者 template 属性,最终都会转换成 render 方法,

4. 最后,调用原先原型上的 $mount 方法挂载。

mount.call(this, el, hydrating)

原型上的 $mount 方法主要是调用了 mountComponent 方法。