一直都想看看 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 时做了什么。
- 判断
Vue方法是否是通过new方法调用的,不是就提示错误。 - 进入初始化
_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 不能挂载在 body、html 这样的根节点上
在新的 $mount 方法中,使用 query 方法找到的是 #app 这个 dom 节点,判断 #app 不能挂载在 body、html 这样的根节点上。
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 方法
- 先对
template进行一些校验。 - 如果没有
template,将el通过template = getOuterHTML(el)转换为template。此段代码中,转换之后的 template 为:
"<div id="app"> {{ message }} </div>"
- 将
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 方法。