Angular 是 Google 亲儿子,React 是 Facebook 小正太,那咱为啥偏偏选择了 Vue 下手,一句话,Vue 是咱见过的最对脾气的 MVVM 框架。之前也使用过 knockout,angular,react 这些框架,但都没有让咱产生 follow 的冲动。直到见到 Vue,简直是一见钟情啊。
Vue 的官方文档已经对 Vue 如何使用提供了最好的教程,建议 Vue 新手直接去官网学习,而不要在网上找些质量参差不齐的文档去看,以免误人子弟。中文版 和 英文版 文档写的都很地道,毕竟是国产,中文文档真心赞。不夸张地说,中文文档咱已经看了不下 5 遍了,每次都有收获,第一遍看的很慢,边看边做,之后就非常快了,主要都是扫读。英文的咱也不是看不了,只是速度问题,没必要较这个劲。值得一提的是,Vue 每次版本更新官方文档迅速都会跟进到最新,所以说 Vue 的官方文档是学习 Vue 的不二之选。
本系列的目的不是介绍如何使用 Vue,而是希望把 Vue 的源码实现思路简单清晰地描绘出来,从而摸清一个 MVVM 框架是如何工作的,并从中学习封装轮子(库或框架)的各种实用技巧。文章中的不足和欠缺之处,请大家多多指教/抱拳。
Vue 的源码目录结构
如果直接去看 Vue 生成的代码文件 Vue.js,代码足足有上万行。这样去研究源码肯定是行不通的,也是不明智的。从 Vue 项目的目录结构入手是个不错的选择,这么大的项目,一个好的目录结构对开发和维护的重要性不言而喻。
Vue 源码目录如下:
Vue 源码目录
各目录和文件功能:
- compiler
此目录存放编译模板相关的代码,用于将 html 模板编译成 Vue 的 render 函数。 - runtime
此目录存放 Vue 生命周期内使用的相关代码,负责 Vue 实例的创建,视图渲染和处理虚拟 DOM 等等一切编译 html 模板之外的事情。 - server
这里存放 Vue 服务端渲染(SSR)需要使用的代码。 - util
这里存放一些其他模块共用的工具函数。 - entry-compiler.js
该文件是一个提供编译 html 模板相关接口的模块,通常用于为 Vue 编写的构建插件,比如vue-loader
或vueify
。 - entry-runtime.js
用于构建仅包含运行时的文件,不具备编译 html 模板功能。 - entry-runtime-with-compiler.js
用于构建同时包含编译器和运行时的全功能文件。 - entry-server-basic-renderer.js 和 entry-server-renderer.js
用于构建服务端渲染可用的文件。
可见 Vue 按照功能块对整个项目进行了目录拆分,每个目录负责一块功能,接下来就可以从这些 entry 文件入手按照 模块依赖 进行由浅及深,从整体到局部的深入剖析。
在继续之前咱们可以先看一下由上面这些源代码构建出来的可用文件是怎么样的。
Vue 构建生成的目录如下:
Vue 构建生成的目录
各文件功能:
UMD | CommonJS | ES Module | |
---|---|---|---|
Full | vue.js | vue.common.js | vue.esm.js |
Runtime-only | vue.runtime.js | vue.runtime.common.js | vue.runtime.esm.js |
表格中的术语解释:
- Full:包含编译器和运行时的全部功能。
- Runtime-only:仅包含运行时。
- UMD:可通过
<script>
标签引入直接在浏览器中使用,Vue 会暴露一个全局变量window.Vue
。同时适配require.js
这种 AMD 系统的使用。 - CommonJS:适配
const Vue = require('vue')
这种 node 式的模块系统。 - ES Module:适配
import Vue from 'vue'
这种 es6 提供的模块系统。
这些被构建出来的文件才是咱们在实际项目中可以直接使用的文件。
是用 Full 还是用 Runtime-only ?
这个需要具体情况具体分析。如果你需要使用 Vue 提供的 html 模板功能,那么就使用 Full 版本。否则,最好用 Runtime-only 版本,因为它比 Full 版本的文件体积会小上 30% 左右。值得注意的是,*.vue
单文件组件会被 vue-loader 或 vueify 直接构建成 JavaScript,并没有使用到 Vue 的编译器,因此可使用 Runtime-only 版本。
预备知识
# Flow
Vue 使用了 Facebook 出品的 flow.js 作为静态类型检查工具,所以特意跑到 flow 的官网了解了一下(不然看到一些奇怪的写法会一脸懵逼),发现其实很简单,就是在变量或函数签名的地方加上一些类型注解,而且都是可选项,加不加都行,主要是为了方便开发维护用的。
为什么选择 FLow 而不是 TypeScript ?
Vue 是想找一个静态类型检查工具以便提高项目的开发效率和可维护性。Flow 和 TypeScript 都提供了静态类型检查功能,但 TypeScript 提供了更多的有用功能,静态类型检查只是 TypeScript 提供的诸多强大功能里并不起眼的一个。而 Flow 则不一样,静态类型检查几乎是它的全部,可以说是典型小而美的实现。可能是本着杀鸡焉用牛刀,尽可能降低项目复杂度的想法,Vue 选择了 Flow 而不是 TypeScript。无独有偶,Vue 的构建工具选择了 rollup 而不是 webpack,原因应该也是如出一辙。所以说最强的不一定是最好的,最合适的才是最好的。
# ES6
Vue 的源码完全使用 ES6 编写,使得代码更清晰,更易维护。本系列的所有代码片段亦全部使用 ES6,不熟悉 ES6 的同学是时候加强学习了,这可是 JavaScript 的未来。如果想系统了解一下 ES6 的话推荐阮一峰老师的 ECMAScript 6 入门 教程。
# Rollup
Vue 使用了 Rollup 作为最后的打包工具。并且使用了 Rollup 的 rollup-plugin-alias 插件,该插件可以为目录取一个别名,使得在编写 ES6 代码 import 模块时可以使用更短的路径,而不用每次都小心翼翼地去拼相对路径,非常方便。如果不了解这一点,在看到 import config from 'core/config'
这种语句时可能会很迷惑,同级目录下并没有 core
目录啊,实际上 core
是 Vue 为其他目录配置的别名。这个映射表可以在 build/alias.js
文件中找到,映射表中有一条 core: resolve('src/core')
,resolve('src/core')
会解析出 core
目录的绝对路径,这其实就是告诉 rollup 在解析 import config from 'core/config'
时从这个绝对目录中去加载 config.js
。
先捡软的捏
从源码的目录结构可以看出,Vue 的 runtime 模块负责的事情很多,代码量必然也很大,应该是块难啃的骨头。而 compiler 则只负责将 html 模板转换为 Vue 的 render 函数,这一块应该是水很浅的,因此从这块入手先吃掉它一部分。
本篇完,将在下篇开始研究 Vue 的编译器模块。
本系列会以每周一篇的速度持续更新,喜欢的小伙伴记得点关注哦。