Vue2 源码
准备工作
下载 2.6.14 版本的源码
先下载仓库,然后切换到 2.6.14
版本
git clone https://github.com/vuejs/vue.git
cd vue
git checkout v2.6.14
安装 vscode 插件
安装 Bookmarks 插件,可以做标记和跳转,方便调试
Flow 类型检查
Vue2 使用 Flow 进行类型检查,而不是 TypeScript。
在 packages.json 的 devDependencies 里可以看到flow-bin
,根目录下还有 .flowconfig
文件。
目录结构
下面是一些主要的源码目录结构:
vue/
├── benchmarks/ # 性能测试相关
├── dist/ # 打包生成的最终文件
├── examples/ # 一些示例项目
├── flow/ # Flow 类型声明文件
│ ├── component.js # 组件相关的类型声明
│ ├── vnode.js # 虚拟DOM相关的类型声明
├── packages/ # Vue 的核心包和其他相关包
│ ├── vue-server-renderer/ # 服务端渲染相关代码
│ ├── vue-template-compiler/ # 模板编译器
├── scripts/ # 构建相关的脚本
├── src/ # 源代码目录,主要代码在这里
│ ├── compiler/ # 编译相关代码,将模板编译为渲染函数
│ │ ├── codegen/ # 代码生成
│ │ ├── directives/ # 编译阶段的指令处理
│ │ ├── parser/ # 模板解析
│ │ ├── create-compiler.js # 创建编译器
│ │ └── index.js # 编译入口
│ │ ├── optimizer.js # 优化器
│ ├── core/ # 核心代码,包含 Vue 的核心功能
│ │ ├── components/ # 内置组件(如 keep-alive)
│ │ ├── global-api/ # 全局 API(如 Vue.use, Vue.mixin)
│ │ ├── instance/ # Vue 实例相关代码
│ │ │ ├── init.js # 实例初始化
│ │ │ ├── state.js # 状态管理
│ │ │ ├── events.js # 事件处理
│ │ │ ├── lifecycle.js # 生命周期
│ │ │ └── render.js # 渲染
│ │ ├── observer/ # 响应式系统实现
│ │ │ ├── array.js # 数组响应式处理
│ │ │ ├── dep.js # 依赖追踪
│ │ │ ├── index.js # 观察者入口
│ │ │ ├── scheduler.js # 调度器
│ │ │ ├── watcher.js # 观察者
│ │ └── util/ # 工具函数
│ │ ├── vdom/ # 虚拟 DOM 实现
│ │ └── create-element.js # 创建虚拟节点
│ │ ├── patch.js # 打补丁(更新 DOM)
│ │ ├── vnode.js # 虚拟节点定义
│ ├── platforms/ # 平台特定代码,如 Web 和 Weex
│ │ ├── web/ # Web 平台相关代码
│ │ │ ├── compiler/ # Web 平台编译器
│ │ │ ├── runtime/ # Web 平台运行时
│ │ │ ├── server/ # Web 平台服务端渲染
│ │ │ └── util/ # Web 平台工具函数
│ │ ├── weex/ # Weex 平台相关代码
│ │ │ ├── compiler/ # Weex 平台编译器
│ │ │ ├── runtime/ # Weex 平台运行时
│ │ │ └── util/ # Weex 平台工具函数
│ ├── server/ # 服务端渲染相关代码
│ │ ├── bundle-renderer/ # bundle 渲染器
│ │ ├── optimizing-compiler/ # 优化编译器
│ │ └── renderer.js # 渲染器
│ ├── sfc/ # 单文件组件 (Single File Component) 解析代码
│ │ ├── parser.js # SFC 解析器
│ └── shared/ # 共享的工具函数
│ ├── util.js # 共享工具函数
├── test/ # 测试文件
└── types/ # TypeScript 类型定义文件
结构说明:
- dist/: 打包生成的最终文件
- examples/: 一些示例项目
- flow/: Flow 类型声明文件,提供静态类型检查
- packages/: Vue 的核心包和其他相关包,如服务端渲染和模板编译器
- scripts/: 构建相关的脚本
- src/: 源代码目录,主要代码在这里
- compiler/: 编译相关代码,将模板编译为渲染函数
- core/: 核心代码,包含 Vue 的核心功能
- platforms/: 平台特定代码,如 Web 和 Weex
- server/: 服务端渲染相关代码
- sfc/: 单文件组件 (Single File Component) 解析代码(.vue 文件)
- shared/: 共享的工具函数
- test/:测试文件
- types/:TypeScript 类型定义文件
快速开始
1. 从入口文件开始
在 src/core
目录下有一个 instance
文件夹,其中的 index.js
是 Vue 实例的入口。这个文件定义了 Vue 构造函数。
先从 src/core/instance/index.js
开始阅读,理解 Vue 实例的创建过程。
这里this._init(options)
的_init
是在 initMixin 方法中定义的Vue.prototype._init = function(){}
2. 理解初始化过程
深入 init
方法,位于 src/core/instance/init.js
。这个文件中定义了 Vue 实例的初始化逻辑,包括挂载、状态初始化、事件初始化等。
3. 编译和渲染
阅读编译和渲染相关的代码:
- 编译阶段: 位于
src/compiler
目录 - 渲染阶段: 位于
src/core/vdom
目录。可以从patch.js
开始,这是虚拟 DOM 的核心实现
4. 响应式原理
阅读 src/core/observer
目录下的代码,从 observer/index.js
开始,理解数据响应式的实现原理。
5. 其他核心功能
- 组件系统: 位于
src/core/instance/init.js
和src/core/vdom/create-component.js
- 指令和插件: 位于
src/core/global-api
目录 - 生命周期: 位于
src/core/instance/lifecycle.js
入口文件
路径:src/core/instance/index.js
1. initMixin
为 Vue 类添加一个 _init
方法,这是每个 Vue 实例初始化的核心入口。
关键操作:
- 唯一标识(uid):为每个 Vue 实例分配一个唯一的 ID
_uid
。 - 性能标记:在非生产环境下,如果开启了性能监控,使用 mark 和 measure 来记录组件初始化的开始和结束时间,便于性能分析。
- 实例标识:设置
_isVue
标志为 true,用于区分 Vue 实例和其他对象。 - 选项合并:根据传入的选项是否包 含
_isComponent
属性决定不同的初始化路径。如果是内部组件,走特殊优化路径;否则,通过mergeOptions
合并构造函数的默认选项和用户提供的选项。 - 代理初始化:根据环境不同,初始化代理对象以实现数据响应式。在开发模式下还会创建代理对象用于调试。
- 生命周期、事件、渲染初始化:分别调用
initLifecycle
、initEvents
、initRender
初始化 Vue 实例的生命周期、事件系统和渲染函数。 - 钩子函数调用:在特定阶段调用生命周期钩子,如
beforeCreate
和created
。 - 依赖注入与状态初始化:调用
initInjections
、initState
、initProvide
初始化依赖注入、组件状态和提供者。 - 性能标记结束(如果开启):记录组件初始化完成的时间点。
- 挂载:如果组件选项中指定了挂载点 (
$options.el
),自动执行挂载操作。
2. initInternalComponent
专门用于初始化内部组件(即在其他组件模板中声明的组件)。
操作:
- 选项对象创建:基于父组件的选项创建一个新的选项对象,并保留对父组件的引用。
- VNode 信息提取:从父组件的虚拟节点中提取组件所需的各种配置,如 props、listeners、children 等。
- 渲染函数设置:如果组件定义了自定义的渲染函数或静态渲染函数,则直接赋值给新选项。
3. resolveConstructorOptions
递归解析构造函数的选项,处理继承关系中的选项合并。
操作:
- 选项获取与更新:首先获取当前构造函数的选项,如果存在父构造函数,则递归调用自身获取父构造函数的最新选项。
- 选项合并:比较当前构造函数的选项与其缓存的密封选项(sealedOptions),如果有差异,则表示选项被修改过,需要将差异部分合并到扩展选项中,最终与父选项合并生成新的选项对象。
- 组件注册:如果新选项中指定了名称,则将该组件注册到全局组件列表中。
4. resolveModifiedOptions
辅助函数,用于找出构造函数选项中与密封选项不同的部分。
操作:遍历最新选项与密封选项,记录所有不匹配的键值对,返回一个包含所有修改过的选项的对象。
模板编译原理
路径:src/compiler
Vue 的模板编译是将模板字符串转换为渲染函数的过程,主要分为以下三个步骤:
- 解析(parse):将模版字符串转换为 AST 抽象语法树。使用正则表达式对 template 字符串进行解析,将标签、指令、属性等转化为 AST
- 优化(optimize):标记静态节点,便于后续的渲染优化。具体是遍历 AST,找到静态节点并标记,在页面重渲染进行 diff 比较时,跳过这些静态节点,优化 runtime 的性能
- 生成代码(generate):将 AST 转换为 render 渲染函数
关键文件和目录
src/compiler/parser/
:模板解析相关代码,将模板字符串解析成 ASThtml-parser.js
:解析 HTML 模板text-parser.js
:解析文本内容,处理插值表达式index.js
:解析入口文件
src/compiler/optimizer.js
:优化相关代码,标记静态节点,优化渲染性能src/compiler/codegen/
:代码生成相关代码,将 AST 转换成渲染函数代码src/compiler/index.js
:编译器的入口文件
源码阅读顺序
-
从编译入口开始:
src/compiler/index.js
:了解编译的整体流程。这个文件定义了createCompiler
函数,调用解析、优化和代码生成三个阶段的函数
-
解析阶段:
src/compiler/parser/index.js
:解析入口文件src/compiler/parser/html-parser.js
:解析 HTML 模板,生成 AST 节点src/compiler/parser/text-parser.js
:解析文本内容,处理插值表达式
-
优化阶段:
src/compiler/optimizer.js
:优化 AST,标记静态节点
-
代码生成阶段:
src/compiler/codegen/index.js
:代码生成入口文件。调用generate
函数,将优化后的 AST 转换成渲染函数代码