Vue 源码解读系列 (一):如何阅读源码

Clloz · · 1,584次浏览 ·

前言

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'corecompiler 两个文件夹是源码中最核心的。在 src/core/index.js 还是没有找到 Vue 构造函数,这次又是从 src/core/instance/index.jsimport 的,不过在 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.$isServerVue.prototype.$ssrContextVue.functionalRenderContextVue.version

到了 src/core/instance/index.js 中就是我们实际的 Vue 构造函数了,我们看到最初的构造函数十分简单,就调用了一个 _init 方法,然后执行了一堆初始化的函数,这些函数也就是在我们的这个简单的 Vue 构造函数以及其 prototype 上增加属性和方法,来提供 Vue 的一些列功能。

总结

本文我分析了如何阅读 Vue 源码的一些经验以及找到了 Vue 的构造函数,其实最终我们使用的那个 Vue 也就是围绕这这个简单的 Vue 函数,其实例和其 prototype 提供的一系列方法和属性,把这几个文件在做了什么搞清楚我们也就知道 Vue 是如何工作的了。

按照 import 的文件先执行,我们就从最内部的 src/core/instance/index.js 开始分析,下一篇文章见。


Clloz

人生をやり直す

发表评论

电子邮件地址不会被公开。 必填项已用*标注

我不是机器人*

 

00:00/00:00