【二】Vue2 源码—Vue 构造函数和初始化
Vue.js 版本为 v2.6.14
Vue 构造函数
平台相关的入口文件
在上一节 Vue.js 源码-项目基础和项目构建 提到 Vue.js 构建过程,在 web 应用下,我们构建完整版的 CommonJS 版本的代码,构建后的 dist/vue.common.js 的打包入口路径是 src/platforms/web/entry-runtime-with-compiler.js。在这个入口文件中我们可以看到:
// ……省略代码
import Vue from './runtime/index'
// ……省略代码
export default Vue我们的业务代码执行到 import Vue from 'vue' 的时候,就是从这个入口导入的 Vue。
通过上面的代码我们可以看到 import Vue from './runtime/index',入口 JS 的 Vue 又是从 src/platforms/web/runtime/index.js 导入的。src/platforms/web/runtime/index.js 代码如下:
import Vue from 'core/index'
// ……省略代码
export default Vue从入口模块到核心模块
通过 import Vue from 'core/index' 可以知道,真正初始化 Vue 的地方是在 src/core/index.js 中:
从代码中我们可以看到 import Vue from './instance/index',从 ./instance/index 导出 Vue,代码在 src/core/instance/index.js 中:
终于在这里我们看到了 Vue 构造函数,我们工作中 new Vue() 实例化的就是它。
Vue 初始化
我们找到了 Vue 构造函数,之后我们就可以从 Vue 构造函数声明的地方开始,看一下 Vue 的初始化过程。根据我们之前寻找 Vue 构造函数的路线,然后从这条路线往回走: src/core/instance/index.js -> src/core/index.js -> src/platforms/web/runtime/index.js -> src/platforms/web/entry-runtime-with-compiler.js
src/core/instance/index.js
先回头看上面贴出完整代码的 src/core/instance/index.js 文件,引入了五个函数,每个函数都以 Mixin 为后缀,然后定义 Vue 构造函数,随后以 Vue 构造函数作为参数,调用了五个引入的函数,最后导出 Vue。这五个函数分别来自 src/core/instance 文件夹下的五个文件:init.js、state.js、events.js、lifecycle.js、render.js。在这五个文件中找到相应的函数,就会发现,这些函数的作用,就是在 Vue 的 prototype 上挂载方法或属性。
src/core/index.js
看完了 src/core/instance/index.js 文件后,我们再往上找到引入 src/core/instance/index.js 的 src/core/index.js 文件。
从 src/core/instance/index 中导入已经在原型上挂载了方法和属性后的 Vue,之后将 Vue 作为参数传递给 initGlobalAPI 函数,然后在 Vue.prototype 上挂载了 $isServer 和 $ssrContext ,最后在 Vue 上挂载了 FunctionalRenderContext 和 version。
initGlobalAPI 的作用是在 Vue 构造函数上挂载静态属性和方法,Vue 经过 initGlobal 之后,再加上 src/core/index.js 自己挂载的,Vue 会扩展为下面的样子:
其中,Vue.options 稍微复杂一点,后面还会进行进一步分析的。此外 Vue.util 暴露的方法最好不要依赖,因为它可能经常会发生变化,是不稳定的。
src/platforms/web/runtime/index.js
再往上就会走到 src/platforms/web/runtime/index.js, 这个文件里的代码主要做了三件事:
覆盖
Vue.config的属性,将其设置为平台特有的一些方法Vue.options.directives和Vue.options.components安装平台特有的指令和组件在 Vue.prototype 上定义
__patch__和$mount
经过 src/platforms/web/runtime/index.js 文件后, Vue 变成了下面这个样子:
此时 Vue.options 又增加了一些平台相关的内容,$mount方法也比较简单,首先如果 el 有值并且是浏览器环境就使用 query(el) 获取元素,然后将 el 作为参数传递给 mountComponent 函数。
src/platforms/web/entry-runtime-with-compiler.js
最后来到了最外层的 src/platforms/web/entry-runtime-with-compiler.js ,该文件做了两件事:
缓存来自
src/platforms/web/runtime/index.js的$mount方法,然后覆盖Vue.prototype.$mount在 Vue 上挂载 compile
compileToFunctions 函数的作用就是将模板 template 编译成 render 函数。
总结
上面是我们初始化 Vue 构造函数的整个过程:
Vue.prototype下的属性和方法的挂载主要是src/core/instance目录下的代码处理的Vue下的静态属性和方法的挂载主要是src/core/global-api目录下的代码处理的src/platforms/web/runtime/index.js主要是添加 web 平台特有的配置、组件和指令,还有在Vue.prototype上挂载__patch__和$mount。src/platforms/web/entry-runtime-with-compiler.js主要是重写了Vue.prototype.$mount方法,添加了 compiler 编译器,支持 template 选项。
具体每一个挂载到 Vue 上的全局 API 和 Vue.prototype 上的实例方法的实现原理,在最后会单独拿出一节来介绍。
扩展
为什么 Vue 不用 ES6 的 Class 实现呢?
我们在 src/core/instance/index.js 文件中可以看到 Vue 构造函数后面有很多 xxxMixin 的函数调用,并把 Vue 当做参数传入,它们的功能都是给 Vue 的 prototype 上扩展一些方法,Vue 按功能把这些扩展分散到多个模块中实现,而不是在一个模块里实现所有的扩展,这种方式是用 Class 难以实现的。这么做的好处是非常方便代码的维护和管理。
参考文档
Last updated