Vue2 源码

Vue2 源码课程简介与调试环境搭建

了解为什么要读 Vue2 源码,掌握源码目录结构与本地调试环境的搭建方法。

🎯 引言

这是「Vue2 源码」课程的第一篇。这门课我们不做 API 使用教学,而是一起打开 Vue2 的源代码,看看那些你每天调用的 datacomputedwatch 背后到底发生了什么。

学完本篇,你将能独立搭建一套 Vue2 源码的本地调试环境,能在浏览器里对源码打断点、单步跟踪,并且对 Vue2 源码的目录结构有一张清晰的地图,知道去哪一层找你关心的问题。


🧱 为什么要读 Vue2 源码

先说一个实在的问题:读源码花时间,值得吗?从我的经验看,它的收益主要在这三件事上。

排查问题更有方向。 你一定遇到过这种情况:代码看起来没问题,但视图就是不更新;或者某个数组改了,模板却没反应。只熟悉 API 用法时,这类问题只能靠试错。读过响应式原理后,你会知道"数组直接通过索引赋值不会触发更新"这类限制是为什么存在,排查时就有明确方向。

写出更合理的代码。 理解了 Dep 和 Watcher 的依赖收集机制,你就明白为什么 computed 比方法更适合做缓存计算,为什么频繁赋值可以合并更新。这些不是背下来的规范,而是知道原理后的自然选择。

理解 API 的设计动机。 Vue2 的很多设计(比如 this.$set、比如 $nextTick)初看有点奇怪,读懂源码后会明白它们是为了解决什么具体问题而存在的。这种理解也会迁移到 Vue3 和其他框架的学习上。

读源码不是为了面试背题,也不是要把每个函数都看懂。带着具体问题去读,读懂主干逻辑就足够受益了。

✨ 版本说明

本课程基于 Vue 2.7.16 的源码讲解。Vue 2.7 是 2.x 系列的最后一个 minor 版本,API 用法与 2.6 基本一致,同时内置了 Composition API 等向后兼容的特性。选择它的原因是:这是 2.x 的最终形态,学到的核心机制(响应式、虚拟 DOM、模板编译)在整个 2.x 生命周期内保持稳定。

如果你项目里用的是 2.6,课程内容同样适用,核心代码路径基本一致。


🧭 源码目录地图

打开 vuejs/vue 仓库的 src 目录,你会看到几个主要文件夹。下面这张目录地图标出了每个目录里最关键的文件,点开文件就能看到它的职责,后面每篇都会深入其中的一部分:

每个目录的职责也可以用一张表速览:

目录职责
src/coreVue 的核心逻辑:实例初始化与生命周期、响应式系统、虚拟 DOM
src/compiler模板编译:把 <template> 模板解析成 AST,再生成 render 函数
src/platforms平台相关代码:把核心能力组装成浏览器里用的 Vue(web 入口)
src/server服务端渲染(SSR)相关逻辑,本课程不涉及
src/sfc单文件组件(.vue 文件)的解析逻辑,属于构建工具的辅助代码
src/shared全局共享的工具函数:类型判断、合并选项等

其中 src/core 是 Vue 的核心逻辑,本课程的主战场observer 是响应式系统,vdom 是虚拟 DOM 与 patch,instance 是实例的初始化与生命周期。

一条简单的记忆路线:模板 → compiler 编译成 render 函数 → vdom 生成虚拟节点 → patch 更新真实 DOM → 数据变化时 observer 通知 Watcher 重新走一遍。这条链路就是 Vue 运行时最核心的主线。


🛠 调试环境搭建

光说不练效果有限,我们现在就把环境搭起来。整个过程只需要几分钟,之后每篇文章你都可以在真实的源码里打断点验证。

本课程在 Node.js v22 环境下验证通过。先把源码拉下来并切换到 2.7.16:

# 克隆仓库(本地 Node.js 版本:v22)
git clone https://github.com/vuejs/vue.git vue2-source
cd vue2-source

# 切换到 v2.7.16
git checkout v2.7.16

# 安装依赖(包版本以仓库 package.json 为准)
npm install
如果遇到依赖安装报错(老仓库的 peer 依赖声明较严格),可以试试 npm install --legacy-peer-deps,或者换一个包管理器,不影响后续调试。

安装完成后,构建一份带 sourcemap 的开发版本:

# 构建 dist/vue.js(带完整警告和 sourcemap,适合调试)
npm run dev

构建完成后,dist/vue.js 就是未压缩的开发版。接着在 examples 目录下新建一个调试页面,比如 examples/debug/01-hello.html

01-hello.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8" />
    <title>Vue2 源码调试</title>
</head>
<body>
    <div id="app">{{ message }}</div>

    <script src="../../dist/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data() {
                return {
                    message: 'hello vue2 source',
                };
            },
        });

        // 两秒后修改数据,方便观察更新流程
        setTimeout(() => {
            debugger;
            app.message = 'data changed';
        }, 2000);
    </script>
</body>
</html>

用浏览器直接打开这个 HTML 文件,打开开发者工具,页面会在 debugger 处停住。然后你就可以:

  1. 在 Sources 面板中找到 webpack:///src/core/instance/index.js(sourcemap 会把 dist 映射回源码)。
  2. initStateobserve 等函数里打上断点。
  3. 单步执行,观察数据变化和函数调用栈。

这就是你未来读源码的标准动作:找到关键函数 → 打断点 → 单步看数据和调用栈

npm run dev 构建的是带 sourcemap 的开发版,适合调试。生产构建(npm run build)会压缩代码、去掉警告,不适合学习源码。

💡 阅读方法建议

最后分享几条实际读源码时比较有用的习惯:

  1. 带着具体问题去读。 不要从第一行开始通读。比如"改了 data 里的数据,视图是怎么更新的?"带着这个问题,你自然会找到 Object.defineProperty 的 setter、Dep.notifyWatcher.run 这条链路。
  2. 从入口往下追。 先找到 new Vue() 的入口(src/core/instance/index.js),再顺着初始化流程一层层深入,比漫无目的地翻文件高效得多。
  3. 配合手写迷你实现验证。 读到一个机制后,自己动手写一个几十行的简化版(比如迷你响应式、迷你 Diff),能写对才是真的懂了。本课程多篇都会安排这样的练习。
  4. 不要纠结细节。 Flow 类型注解、边缘兼容分支可以跳过,关注主干逻辑即可。课程中的源码引用也做了教学简化,去掉了类型注解和非核心分支。

🗺 课程路线图

本课程共 8 篇,按"先主干、再深入"的顺序展开:

  1. 简介与环境搭建(本篇):目录地图与调试环境。
  2. 初始化流程new Vue() 时到底发生了什么,_init 的执行顺序。
  3. 响应式原理Object.defineProperty 如何拦截读写,数组为什么需要特殊处理。
  4. Dep 与 Watcher:依赖收集和派发更新,数据变化如何通知到视图。
  5. 异步更新与 nextTick:批量更新队列、微任务与宏任务。
  6. 虚拟 DOM 与 patch:VNode 的结构、patch 的对比更新过程。
  7. 模板编译:模板 → AST → render 函数的三段式流程。
  8. computed 与 watch 原理:两种 Watcher 的实现差异与懒计算机制。
本课程假设你已经熟悉 Vue2 的日常使用(模板语法、组件、生命周期)。如果还没系统学过,建议先完成 Vue2 使用课程。虚拟 DOM 篇会用到 Diff 思想,建议先修 Snabbdom 课程

🧾 小节总结

  • 读 Vue2 源码的价值在于:排查问题更有方向、写出更合理的代码、理解 API 设计动机。
  • 课程基于 Vue 2.7.16,这是 2.x 的最后一个 minor 版本,用法与 2.6 基本一致。
  • 源码目录中,src/coresrc/compiler 是主线,其他目录按需了解即可。
  • 调试环境三步走:克隆仓库并切到 v2.7.16 → npm run dev 构建开发版 → 在 examples 里写页面用浏览器断点调试。
  • 读源码的有效方法是:带着具体问题、从入口往下追、配合手写迷你实现验证。

❓ 知识问答

Q1:为什么选 Vue2 而不是直接读 Vue3 源码?

A:Vue3 目前更主流,但 Vue2 的代码量更小、结构更直观,适合作为读源码的入门材料。而且响应式、虚拟 DOM、模板编译这些核心思想在两个版本里是相通的,先读 Vue2 再看 Vue3 会更容易抓住变化点。

Q2:我不会用浏览器的断点调试,影响学习吗?

A:不影响读文章本身,但建议花几分钟熟悉 Chrome DevTools 的 Sources 面板,学会打断点和单步执行。这是读源码的基本工具,之后会频繁用到。

Q3:npm run devnpm run build 有什么区别?

A:npm run dev 构建带 sourcemap 的未压缩版本,保留完整警告信息,适合调试学习。npm run build 构建压缩后的生产版本,去掉了警告,不适合读源码。

Q4:课程中引用的源码和仓库里的代码完全一样吗?

A:做了教学简化。去掉了 Flow 类型注解和一些边界分支,保留核心逻辑。每处引用都会标注"简化自"哪个文件,方便你对照原始代码。

Q5:读源码一定要手写迷你实现吗?

A:不是必须,但强烈推荐。看懂和能写出来是两回事,动手写一个几十行的简化版,是检验理解的可靠方式。


🧪 小练习

搭建好环境后,试着完成这个练习:在 examples/debug/01-hello.html 中,给 data 添加一个 count 字段并渲染到页面上,然后在浏览器断点中观察 new Vue() 的初始化过程。

const app = new Vue({
    el: '#app',
    data() {
        return {
            message: 'hello vue2 source',
            // 请在这里编写代码:添加 count 字段
        };
    },
});

// 请在这里编写代码:三秒后修改 count,并在修改前加 debugger

完成后在 Sources 面板中找到 src/core/instance/init.js,在 initState 调用处打断点,观察初始化流程。


🎉 恭喜你已经搭好 Vue2 源码的调试环境,也有了完整的课程地图!下一篇我们从 new Vue() 开始,一步步跟踪初始化流程。