Vue 源码解读系列 (一):如何阅读源码
前言
Vue
作为在国内最热门的前端框架,网上的教程一堆,也有不少写的不错的,比如以前看的 《深入浅出 Vue.js》就讲的不错。不过和大部分上网浏览的知识一样,好讲的东西基本大家都讲到了,不好讲的或一些细节大家就都没讲到。而且大部分文章或源码解析都是将 Vue
中的几个比较重要的模块单独来讲了一下,比如数据响应式原理,虚拟 DOM
,大家都能说上两句,但除了这些“通俗”的内容,其他的基本都没讲,看完这些教程你就知道了 Vue
中的一些模块,整个 Vue
是怎么工作的还是不清楚。我想写一个系列文章结合实际的例子讲清楚 Vue
的整个执行过程。
本文所使用的是 Vue2.0 的最新版本 2.6.14
目录结构分析
```
├── benchmarks 一些特殊复杂场景的跑分 demo 比如大数据量表格或者大量元素的 svg 的渲染
├── dist rollup 的输出目录,不同环境的 vue 文件
├── examples 例子
├── flow flow 的类型声明文件
├── packages
├── scripts 脚本,对应 package.json 的 scripts
├── src
│ ├── compiler 模版编译相关
│ │ ├── codeframe.js
│ │ ├── codegen
│ │ ├── create-compiler.js
│ │ ├── directives
│ │ ├── error-detector.js
│ │ ├── helpers.js
│ │ ├── index.js
│ │ ├── optimizer.js
│ │ ├── parser
│ │ └── to-function.js
│ ├── core
│ │ ├── components 组件相关
│ │ ├── config.js
│ │ ├── global-api 全局 API
│ │ ├── index.js
│ │ ├── instance 实例化
│ │ ├── observer 数据响应式
│ │ ├── util 工具方法
│ │ └── vdom 虚拟 dom
│ ├── platforms
│ │ ├── web
│ │ └── weex
│ ├── server
│ │ ├── bundle-renderer
│ │ ├── create-basic-renderer.js
│ │ ├── create-renderer.js
│ │ ├── optimizing-compiler
│ │ ├── render-context.js
│ │ ├── render-stream.js
│ │ ├── render.js
│ │ ├── template-renderer
│ │ ├── util.js
│ │ ├── webpack-plugin
│ │ └── write.js
│ ├── sfc
│ │ └── parser.js
│ └── shared
│ ├── constants.js
│ └── util.js
├── test
└── types
```
如何阅读源码
源码阅读肯定不能从上到下一个一个看,我的目的是了解 Vue
是如何工作的,Vue
说到底也就是一个大函数对象,我其实就是想搞清楚这个函数上有哪些属性,我们 new Vue
的时候函数内部执行了些什么。
那么怎么找到这个函数呢?刚开始看源码的时候我们就会开始迷茫,入口在哪里?因为 Vue
的这些源码最后会用 rollup
打包成一个文件(根据不同的环境会打包出很多文件,在 dist
目录中)。我们可以通过 package.json
中的脚本来找到入口文件,比如第一个脚本是 "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev"
,虽然我们不懂 rollup
,但是根据 webpack
的使用经验也能估计出来这行脚本的意思就是用 scripts
目录下的 config.js
文件作为配置文件执行 rollup
。后面有个 TARGET:web-full-dev
我们到配置文件中一搜就有了下面这段:
// Runtime+compiler development build (Browser)
'web-full-dev': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.js'),
format: 'umd',
env: 'development',
alias: { he: './entity-decoder' },
banner
},
我们可以看到在配置文件的 build
对象中有很多配置,对应的就是 TARGET
,这里的配置就是 vue
根据不同环境编写的,由于我们是阅读源码肯定是从 dev
来看,配置文件中的 entry
字段告诉了我们入口文件在 web/entry-runtime-with-compiler.js
。
这里有一点需要提醒,我们在 IDE
可能会发现 Vue
源码中有些 import
的文件路径无法跳转,这是因为这些路径都是写给 rollup
的路径,在 rollup
的配置文件中有 alias
的配置
const resolve = p => path.resolve(__dirname, '../', p)
module.exports = {
vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
compiler: resolve('src/compiler'),
core: resolve('src/core'),
shared: resolve('src/shared'),
web: resolve('src/platforms/web'),
weex: resolve('src/platforms/weex'),
server: resolve('src/server'),
sfc: resolve('src/sfc')
}
const aliases = require('./alias')
const resolve = p => {
const base = p.split('/')[0]
if (aliases[base]) {
return path.resolve(aliases[base], p.slice(base.length + 1))
} else {
return path.resolve(__dirname, '../', p)
}
}
目前我没找到在 webstorm
或者 VS Code
里面解析 rollup alias
的方法,webstorm
中可以写个 webpack
的配置文件来达到这个目的,不过文件的解构不复杂,这点小问题也不影响我们分析。
找到 Vue 构造函数
我们回到上面的入口文件,在入口文件中我们能够看到有一个 import Vue from './runtime/index'
,我们去 src/platforms/web/runtime/index.js
中发现第一行就是 import Vue from 'core/index'
,core
和 compiler
两个文件夹是源码中最核心的。在 src/core/index.js
还是没有找到 Vue
构造函数,这次又是从 src/core/instance/index.js
中 import
的,不过在 src/core/instance/index.js
中我们最终找到了 function Vue
也就是 Vue
的构造函数。
我们梳理一下我们从入口文件到最终找到的 Vue
构造函数所造的文件一共有四个文件,我们来扫一扫这几个文件都做了什么,不用特别仔细。
src/platforms/web/entry-runtime-with-compiler.js
文件中主要就是编写了一个 Vue.prototype.$mount
方法,这里有个小细节我们在之后阅读源码的时候要注意的就是 const mount = Vue.prototype.$mount
这行代码。
src/platforms/web/runtime/index.js
文件中我们发现它也定义了 Vue.prototype.$mount
方法,看上去似乎是重复定义了,这里就可以看出上面说道的那行代码的作用了,由于这个文件是被 entry-runtime-with-compiler.js
引入的,所以先执行,这个文件中定义的 Vue.prototype.$mount
方法最后会被重新赋值到一个 mount
变量中。至于为什么要这么做,以及每个函数是做什么的我们后面再分析。该文件还定义了一些 Vue.config
的属性和 Vue.prototype.__patch__
。
src/core/index.js
中最重要的就是 initGlobalAPI
方法的调用,从这个方法的名字我们就能知道它是初始化全局 API
的。同时还定义了四个属性,Vue.prototype.$isServer
,Vue.prototype.$ssrContext
,Vue.functionalRenderContext
和 Vue.version
。
到了 src/core/instance/index.js
中就是我们实际的 Vue
构造函数了,我们看到最初的构造函数十分简单,就调用了一个 _init
方法,然后执行了一堆初始化的函数,这些函数也就是在我们的这个简单的 Vue
构造函数以及其 prototype
上增加属性和方法,来提供 Vue
的一些列功能。
总结
本文我分析了如何阅读 Vue
源码的一些经验以及找到了 Vue
的构造函数,其实最终我们使用的那个 Vue
也就是围绕这这个简单的 Vue
函数,其实例和其 prototype
提供的一系列方法和属性,把这几个文件在做了什么搞清楚我们也就知道 Vue
是如何工作的了。
按照 import
的文件先执行,我们就从最内部的 src/core/instance/index.js
开始分析,下一篇文章见。