跳到主要内容

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.jssrc/core/vdom/create-component.js
  • 指令和插件: 位于 src/core/global-api 目录
  • 生命周期: 位于 src/core/instance/lifecycle.js

入口文件

路径:src/core/instance/index.js

1. initMixin

为 Vue 类添加一个 _init 方法,这是每个 Vue 实例初始化的核心入口。

关键操作:

  1. 唯一标识(uid):为每个 Vue 实例分配一个唯一的 ID _uid
  2. 性能标记:在非生产环境下,如果开启了性能监控,使用 mark 和 measure 来记录组件初始化的开始和结束时间,便于性能分析。
  3. 实例标识:设置 _isVue 标志为 true,用于区分 Vue 实例和其他对象。
  4. 选项合并:根据传入的选项是否包含 _isComponent 属性决定不同的初始化路径。如果是内部组件,走特殊优化路径;否则,通过 mergeOptions 合并构造函数的默认选项和用户提供的选项。
  5. 代理初始化:根据环境不同,初始化代理对象以实现数据响应式。在开发模式下还会创建代理对象用于调试。
  6. 生命周期、事件、渲染初始化:分别调用 initLifecycleinitEventsinitRender 初始化 Vue 实例的生命周期、事件系统和渲染函数。
  7. 钩子函数调用:在特定阶段调用生命周期钩子,如 beforeCreatecreated
  8. 依赖注入与状态初始化:调用 initInjectionsinitStateinitProvide 初始化依赖注入、组件状态和提供者。
  9. 性能标记结束(如果开启):记录组件初始化完成的时间点。
  10. 挂载:如果组件选项中指定了挂载点 ($options.el),自动执行挂载操作。

2. initInternalComponent

专门用于初始化内部组件(即在其他组件模板中声明的组件)。

操作:

  • 选项对象创建:基于父组件的选项创建一个新的选项对象,并保留对父组件的引用。
  • VNode 信息提取:从父组件的虚拟节点中提取组件所需的各种配置,如 props、listeners、children 等。
  • 渲染函数设置:如果组件定义了自定义的渲染函数或静态渲染函数,则直接赋值给新选项。

3. resolveConstructorOptions

递归解析构造函数的选项,处理继承关系中的选项合并。

操作:

  • 选项获取与更新:首先获取当前构造函数的选项,如果存在父构造函数,则递归调用自身获取父构造函数的最新选项。
  • 选项合并:比较当前构造函数的选项与其缓存的密封选项(sealedOptions),如果有差异,则表示选项被修改过,需要将差异部分合并到扩展选项中,最终与父选项合并生成新的选项对象。
  • 组件注册:如果新选项中指定了名称,则将该组件注册到全局组件列表中。

4. resolveModifiedOptions

辅助函数,用于找出构造函数选项中与密封选项不同的部分。

操作:遍历最新选项与密封选项,记录所有不匹配的键值对,返回一个包含所有修改过的选项的对象。

模板编译原理

路径:src/compiler

Vue 的模板编译是将模板字符串转换为渲染函数的过程,主要分为以下三个步骤:

  1. 解析(parse):将模版字符串转换为 AST 抽象语法树。使用正则表达式对 template 字符串进行解析,将标签、指令、属性等转化为 AST
  2. 优化(optimize):标记静态节点,便于后续的渲染优化。具体是遍历 AST,找到静态节点并标记,在页面重渲染进行 diff 比较时,跳过这些静态节点,优化 runtime 的性能
  3. 生成代码(generate):将 AST 转换为 render 渲染函数

关键文件和目录

  • src/compiler/parser/:模板解析相关代码,将模板字符串解析成 AST
    • html-parser.js:解析 HTML 模板
    • text-parser.js:解析文本内容,处理插值表达式
    • index.js:解析入口文件
  • src/compiler/optimizer.js:优化相关代码,标记静态节点,优化渲染性能
  • src/compiler/codegen/:代码生成相关代码,将 AST 转换成渲染函数代码
  • src/compiler/index.js:编译器的入口文件

源码阅读顺序

  1. 从编译入口开始

    • src/compiler/index.js:了解编译的整体流程。这个文件定义了 createCompiler 函数,调用解析、优化和代码生成三个阶段的函数
  2. 解析阶段

    • src/compiler/parser/index.js:解析入口文件
    • src/compiler/parser/html-parser.js:解析 HTML 模板,生成 AST 节点
    • src/compiler/parser/text-parser.js:解析文本内容,处理插值表达式
  3. 优化阶段

    • src/compiler/optimizer.js:优化 AST,标记静态节点
  4. 代码生成阶段

    • src/compiler/codegen/index.js:代码生成入口文件。调用 generate 函数,将优化后的 AST 转换成渲染函数代码