从拷源码开始首先,想搞清楚 Vue2 的源码,至少要把源码拷下来吧:
1 git clone --branch v2.6.14 https://github.com/vuejs/vue.git
我们这里选用的是最新版本 2.6.14 的源代码。
目录结构简介1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 vue ├── dist # 构建后的文件 ├── examples # 例子 ├── flow # flow 类型声明相关 ├── packages │ ├── vue-server-renderer │ ├── vue-template-compiler │ ├── weex-template-compiler │ └── weex-vue-framework ├── README.md ├── scripts ├── src │ ├── compiler # 模板编译器 │ ├── core # 核心代码,与平台无关 │ │ ├─observe # 变化侦听 │ │ ├─vdom # 虚拟DOM相关代码 │ │ ├─instance # Vue 实例相关代码 │ │ ├─global-api # 全局API相关代码 │ │ └─components # 内置组件相关代码 │ ├── platforms # 面向不同平台,不同环境的入口文件 │ ├── server # 服务器渲染相关代码 │ ├── sfc # 单文件组件 │ └── shared # 共享工具库 ├── test # 测试代码 ├── types # 类型声明文件
我们学习 Vue2 源码最重要的是理解它的核心逻辑,所以我们最重要的是关注 src/core 和 src/complier 这两个文件夹。
在阅读源码的过程中,我们只需要搞清楚它的核心逻辑和流程,没有必要去纠结于过于细节以及和平台相关的东西。
寻找入口Vue 的核心入口文件就在 src/core/index.js,如果不想看寻找过程的可以跳过
拿到一个开源项目,首先就是找到它的入口文件。通过 dist 目录我们可以发现,它产出了很多的文件。这意味着 Vue 本身是有很多个入口的。
我们现在可以简要的看看像 scripts 目录和 package.json,通过这两块来寻找它的入口。
先看 package.json,寻找 build 相关的命令:
1 2 3 4 5 { "build" : "node scripts/build.js" , "build:ssr" : "npm run build -- web-runtime-cjs,web-server-renderer" , "build:weex" : "npm run build -- weex" }
还是那句话,我们暂时不考虑与平台相关的。很明显,build:ssr 和 build:weex 这两个是和平台有关的(一个服务端渲染,一个移动端框架),暂时不管了,我们就看第一个。
从这段代码我们就发现了 script/build.js 这个文件,我们立马打开一看。点击打开这个文件
可能这个文件有点复杂,但是我们看到这个文件里面有一个直接调用的 build 函数:
那么 builds 是从哪儿来的呢?往上一看:
1 let builds = require ('./config' ).getAllBuilds ()
接下来我们来打开 config.js ,迅速找到 getAllBuilds:点击跳转
可以看到:
1 exports .getAllBuilds = () => Object .keys (builds).map (genConfig)
这个代码就是对 builds 这个对象进行了一个简单的遍历操作。
所以接下来看 builds:点击跳转
一切豁然开朗了,所有的构建目标似乎都在这里了。
我们以第一个目标为例:
1 2 3 4 5 6 7 8 9 { 'web-runtime-cjs-dev' : { entry : resolve ('web/entry-runtime.js' ), dest : resolve ('dist/vue.runtime.common.dev.js' ), format : 'cjs' , env : 'development' , banner } }
entry 就是它的入口位置。
但是很不巧的是,这里的路径似乎都是一些相对路径,不过我们可以使用 vscode 的文件搜索(Ctrl+P)来找到 web/entry-runtime.js:点击跳转
这个文件的代码非常简单,就是直接把 Vue 从 ./runtime/index 引入又导出了:
1 2 3 4 5 import Vue from './runtime/index' export default Vue
接下来我们找到 ./runtime/index:点击跳转
可以看到第一行代码就是:
1 import Vue from 'core/index'
仔细观察文件可以发现其后面的一些工作都是做一些适配、提示等辅助性的工作。最后导出也是这个 Vue。
所以,我们接下来打开/src/core/index.js :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import Vue from './instance/index' import { initGlobalAPI } from './global-api/index' import { isServerRendering } from 'core/util/env' import { FunctionalRenderContext } from 'core/vdom/create-functional-component' initGlobalAPI (Vue )Object .defineProperty (Vue .prototype , '$isServer' , { get : isServerRendering }) Object .defineProperty (Vue .prototype , '$ssrContext' , { get () { return this .$vnode && this .$vnode .ssrContext } }) Object .defineProperty (Vue , 'FunctionalRenderContext' , { value : FunctionalRenderContext }) Vue .version = '__VERSION__' export default Vue
结果这似乎仍然在做一些辅助性的工作,我们接着打开/src/core/instance/index.js :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import { initMixin } from './init' import { stateMixin } from './state' import { renderMixin } from './render' import { eventsMixin } from './events' import { lifecycleMixin } from './lifecycle' import { warn } from '../util/index' function Vue (options) { if (process.env .NODE_ENV !== 'production' && !(this instanceof Vue ) ) { warn ('Vue is a constructor and should be called with the `new` keyword' ) } this ._init (options) } initMixin (Vue )stateMixin (Vue )eventsMixin (Vue )lifecycleMixin (Vue )renderMixin (Vue )export default Vue
终于看到 Vue 的定义了,这就是入口文件。