Vue 初始化
初始化的过程
platforms 目录下是和平台相关的代码。
先查看 src/platforms/web 下打包 Vue 的入口文件(entry-*.js):
- entry-runtime.js 打包运行时版本的入口文件
- entry-runtime-with-compiler.js 打包完整版的入口文件
// entry-runtime.js 源码只有两行
// 导入Vue的构造函数
import Vue from './runtime/index'
// 导出
export default Vue
/src/platforms/web/entry-runtime-with-compiler.js
/* @flow */
// ...
// 导入 Vue 构造函数
import Vue from './runtime/index'
// ...
// 获取 $mount 初始定义
const mount = Vue.prototype.$mount
// 重写 $mount 方法,增加功能
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// ...
// 判断是否定义了 render 选项
if (!options.render) {
// 如果没有定义 render 函数,获取tempalte,编译成render函数
// 这里是 $mount 方法核心的部分
// ...
}
// 调用 mount 方法,渲染DOM
return mount.call(this, el, hydrating)
}
// 辅助方法
function getOuterHTML (el: Element): string {
// ...
}
// 定义Vue静态方法 compiler:把html字符串编译成render函数
Vue.compile = compileToFunctions
// 导出构造函数
export default Vue
文件中的主要内容是:
- 导入 Vue 构造函数
- 重写Vue实例的$mount方法
- 内部将template转化成render函数
- 导出 Vue 构造函数
- 它没有定义 Vue 构造函数
继续寻找定义 Vue 构造函数的地方。
/src/platforms/web/runtime/index
/* @flow */
// 导入模块
import Vue from 'core/index'
// ...
// install platform specific utils
// 向Vue.config注册了一些 和平台相关的特定的通用的 方法
// 这些方法在 Vue 内部使用,外部很少使用
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement
// install platform runtime directives & components
// 通过extend方法,注册了一些全局的指令和组件
// extend 方法 用于复制对象成员。将第二个参数对象中的所有成员,拷贝到第一个参数对象中
// 通过这段代码可以发现,全局的指令和组件,都存储在 Vue.options 中
// 注册组件 transition transition-group
extend(Vue.options.directives, platformDirectives)
// 注册指令 v-model v-show
extend(Vue.options.components, platformComponents)
// install platform patch function
// 在 Vue 的原型上注册 __patch__ 函数
// 类似 Snabbdom 的 patch 函数:把虚拟DOM转化成真实DOM
// inBrowser:判断是否是浏览器环境;noop:空函数
Vue.prototype.__patch__ = inBrowser ? patch : noop
// public mount method
// 在 Vue 的原型上注册 $mount 方法
// 也就是在注册 Vue 实例上的方法
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
// 调用 mountComponent:渲染DOM
return mountComponent(this, el, hydrating)
}
// devtools global hook
/* istanbul ignore next */
// 和调试相关的代码(暂不关心)
if (inBrowser) {
// ...
}
// 导出 Vue 构造函数
export default Vue
- 注册了和平台相关的指令和组件
- 注册了
__patch__和$mount两个方法 - 依然没有定义 Vue 的构造函数
/src/core/index
源码中使用 core/index的方式加载这个文件,语法看上去在加载一个模块。
flow的配置文件.flowconfig中name_mapper匹配了名为 core 的模块,并将地址指向了/src/core别名,所以core/index指向/src/core/index。
// core/index.js
import Vue from './instance/index'
import {
initGlobalAPI } from './global-api/index'
import {
isServerRendering } from 'core/util/env'
import {
FunctionalRenderContext } from 'core/vdom/create-functional-component'
// 给 Vue 挂载静态方法(这是这个文件的核心部分)
initGlobalAPI(Vue)
// 调用 Object.defineProperty 给 Vue 原型增加一些成员
// 这些成员都和 SSR(服务端渲染) 相关(暂时不关心)
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
/* istanbul ignore next */
return this.$vnode && this.$vnode.ssrContext
}
})
// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
value: FunctionalRenderContext
})
// Vue的版本
Vue.version = '__VERSION__'
// 导出
export default Vue
// core/global-api/index.js
/* @flow */
import config from '../config'
import {
initUse } from './use'
import {
initMixin } from './mixin'
import {
initExtend } from './extend'
import {
initAssetRegisters } from './assets'
import {
set, del } from '../observer/index'
import {
ASSET_TYPES } from 'shared/constants'
import builtInComponents from '../components/index'
import {
observe } from 'core/observer/index'
import {
warn,
extend,
nextTick,
mergeOptions,
defineReactive
} from '../util/index'
export function initGlobalAPI (Vue: GlobalAPI) {
// config
const configDef = {
}
configDef.get = () => config
if (process.env.NODE_ENV !== 'production') {
configDef.set = () => {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
)
}
}
// 初始化 Vue.config 对象
Object.defineProperty(Vue, 'config', configDef)
// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
// 定义 Vue.util:一些公用的方法
// 上面英文翻译:这些工具方法不视作全局 API 的一部分,除非你已经意识到某些风险,否则不要去依赖它们
// 也就是调用这个方法可能会发生意外,不建议外部使用
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
// 定义一些外部常用的静态方法
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 2.6 explicit observable API
// 定义 observable 方法:让一个对象可响应
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
// 初始化 Vue.options 对象,并设置一些属性
// components/directives/filters
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue
// 设置 keep-alive 组件
extend(Vue.options.components, builtInComponents)
// 注册 Vue.use() 用来注册插件
initUse(Vue)
// 注册 Vue.mixin() 实现混入
initMixin(Vue)
// 注册 Vue.extend() 基于传入的options返回一个组件的构造函数
initExtend(Vue)
// 注册 Vue.directive()、Vue.component()、Vue.filter()
initAssetRegisters(Vue)
}
- core/index 与平台无关
- 主要部分就是 给 Vue 挂载静态方法
- 内部没有定义 Vue 的构造函数。
core/instance/index
// ./instance/index.js
import {
initMixin } from './init'
import {
stateMixin } from './state'
import {
renderMixin } from './render'
import {
eventsMixin } from './events'
import {
lifecycleMixin } from './lifecycle'
import {
warn } from '../util/index'
// Vue 的构造函数
// 此处不用 class 的原因是为了方便后续给 Vue 实例混入实例成员
function Vue (options) {
// 判断是否是开发环境
// 通过this判断,是否通过 new 使用 Vue 构造函数
// 如果当作普通函数调用 -> Vue() 就发出警告
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// 调用 _init() 方法
this._init(options)
}
// 注册 vm 的 _init() 方法:初始化 vm
initMixin(Vue)
// 继续混入(注册) vm 的成员:$data/$props/$set/$delete/$watch
stateMixin(Vue)
// 初始化事件相关方法
// $on/$once/$off/$emit
eventsMixin(Vue)
// 初始化生命周期相关的混入方法
// _update/$forceUpdate/$destroy
lifecycleMixin(Vue)
// 混入 render
// $nextTick/_render
renderMixin(Vue)
export default Vue
- 与平台无关
- 创建了 Vue 的构造函数
- 设置 Vue 实例的成员
为何 Vue 不用 ES6 的 Class 去实现
从源码中看到后面有很多 xxxMixin 的函数调用,并把Vue作为参数传入。
它们的功能都是给 Vue 的prototype 上扩展一些方法,也就是向 Vue 实例混入成员。
Vue 按功能把这些扩展分散到多个模块中去实现,而不是在一个模块里实现所有。
这么做的好处是方便代码的维护和管理。
这种方式用 Class (的标准用法)难以实现。
Class 创建的类也可以通过prototype添加成员,这在写法上违背了它的语法糖。
Class的标准用法:
- 一个类就是一个模块
- 所有成员都应该在类内部定义,或继承自父类,而不是使用 prototype 定义成员
- prototype 不应该和 Class 搭配使用
所以这里相对于它的标准用法,function要方便。
// 非标准用法
class Vue {
}
Vue.prototype.$set = () => {
}
// 标准用法1:继承
class GlobalApi {
$set() {
}
}
class Vue extends GlobalApi {
}
// 标准用法2:直接定义
class Vue {
$set() {
}
}
总结:四个导出 Vue 的模块
- src/platforms/web/entry-runtime-with-compiler.js
- web 平台相关的入口,核心就是增加了编译的功能
- 重写了平台相关的 $mount 方法
- $mount方法内部编译模板,将template 转换成 render函数
- 注册了 Vue.compile() 方法
- 传递一个 HTML 字符串 返回 render 函数
- 重写了平台相关的 $mount 方法
- web 平台相关的入口,核心就是增加了编译的功能
- src/platforms/web/runtime/index.js
- 与 web 平台相关
- 注册和平台相关的全局指令,挂载到 Vue.options.directives
- v-model
- v-show
- 注册和平台相关的全局组件,挂载到 Vue.options.components
- transition
- transition-group
- 定义了两个全局方法:
- __patch__ :把虚拟DOM转换成真实DOM
- $mount:挂载方法,把DOM渲染到界面中
- 该文件中定义 $mount
- 在入口文件中重写了 $mount,增加了编译的能力
- src/core/index.js
- 与平台无关
- 核心作用:
- 内部直接设置了一些静态方法
- 还调用 initGlobalAPI(Vue) 定义了一些静态方法
- src/core/instance/index.js
- 与平台无关,与实例(instance)相关
- 内部定义了Vue的构造函数,构造函数内部调用了 this._init(options) 方法
- _init 相当于整个程序的入口
- 给Vue中混入了常用的实例成员
之后会详细查看具体注册了哪些静态成员和实例成员。
两个问题
语言检查
vscode中查看源码中的 Flow 语法时(例如声明类型 Vue: GlobalAPI)会有红色波浪线,提示:"types" 只能在 .ts 文件中使用。
vscode 和 TypeScript 都是微软开发的,所以 vscode 默认支持 TS 语法。
TypeScript 与 Flow 语法类似,而 vscode 不支持 Flow,所以这里 vscode 将其识别为 TS 语法。
TS的语法只能在.ts文件中使用,当前是 .js 文件,所以判定为校验错误。
解决方法:在 vscode 配置中关闭 JS 语法校验:
// settings.json
// 设置不检查 js 的语法问题,防止 flow 报错
"javascript.validate.enable": false
泛型影响后面代码高亮
在查看src\core\global-api\index.js源码时,有一段代码使用了泛型。之后的代码:
- 没有高亮显示了
- 几乎无法使用 转到定义
Ctrl + 左键的功能

因为vscode在识别到泛型的时候会有问题。
解决高亮的方法:安装插件 Babel JavaScript

转到定义Ctrl + 左键 暂时没有办法解决。

静态成员
在src\core\index.js文件中调用的 initGlobalAPI方法中,初始化了 Vue 的大部分静态成员。
initGlobalAPI
注册全局API。
查看 src\core\global-api\index.js 中定义的这个方法:
- 初始化了 Vue.config
- 之后在
src\platforms\web\runtime\index.js中向Vue.config 挂载了一些方法
- 之后在

- 设置了 Vue.util,挂载了一些API
- 这些 API 用于内部使用,官方文档未提供,也不推荐使用。(不用关心)
- 定义了常用的方法:
- set
- delete
- nextTick
- observable
- 初始化了 Vue.options
- 先使用
Object.create(null)初始化这个对象,表示这个对象不需要原型,可以提高它的性能 - 然后遍历
ASSET_TYPES,获取属性名并初始化为不需要原型的空对象- 由于泛型使用,此处无法通过 转到定义 查看
ASSET_TYPES,需要手动查找。
- 由于泛型使用,此处无法通过 转到定义 查看
ASSET_TYPES初始化的3个成员:components/directives/filters- 用于存储全局的组件、指令、过滤器
- 通过 ``Vue.componet
、Vue.directive、Vue.filter注册的全局的组件、指令、过滤器,都会存储到Vue.options.[components/directives/filters]`中去。
- 先使用
- 在
Vue.options._base中记录 Vue 的构造函数- weex 场景中使用
- 使用 extend 注册内置组件 keep-alive
- extend方法:将对象的所有属性(参数1),(浅)拷贝到另一个对象中(参数2)
- extend方法定义位置:
src\shared\util.js- 这个文件中定义了很多通用的方法
- 最后调用几个函数,分别为Vue注册了几个静态方法
- Vue.use
- Vue.mixin
- Vue.extend
- Vue.component、Vue.directive、Vue.filter
// src\core\global-api\index.js
/* @flow */
import config from '../config'
import {
initUse } from './use'
import {
initMixin } from './mixin'
import {
initExtend } from './extend'
import {
initAssetRegisters } from './assets'
import {
set, del } from '../observer/index'
import {
ASSET_TYPES } from 'shared/constants'
import builtInComponents from '../components/index'
import {
observe } from 'core/observer/index'
import {
warn,
extend,
nextTick,
mergeOptions,
defineReactive
} from '../util/index'
export function initGlobalAPI (Vue: GlobalAPI) {
// config
// 首先定义 config 属性
const configDef = {
}
// 定义 config 的属性描述符
configDef.get = () => config
if (process.env.NODE_ENV !== 'production') {
// 开发环境增加一个 set 方法,禁止给 config 属性直接赋值
// 之后在`src\platforms\web\runtime\index.js`中向Vue.config 挂在了一些方法
configDef.set = () => {
warn(
// 警告:不要对Vue.config重新赋值,可以挂载或修改对象中的属性。
'Do not replace the Vue.config object, set individual fields instead.'
)
}
}
// 初始化 Vue.config 对象
Object.defineProperty(Vue, 'config', configDef)
// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
// 定义 Vue.util:一些公用的方法
// 上面英文翻译:这些工具方法不视作全局 API 的一部分,除非你已经意识到某些风险,否则不要去依赖它们
// 也就是调用这个方法可能会发生意外,不建议外部使用
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
// 定义一些外部常用的静态方法
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 2.6 explicit observable API
// 定义 observable 方法:让一个对象可响应
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
// 初始化 Vue.options 对象,挂载3个成员,并初始化为空对象
// components/directives/filters
// 3个成员用于存储全局的组件、指令、过滤器
// Object.create(null) 没有原型,可以提高性能
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
// 记录 Vue 构造函数到 _base
Vue.options._base = Vue
// 注册内置组件 keep-alive
extend(Vue.options.components, builtInComponents)
// 注册 Vue.use() 用来注册插件
initUse(Vue)
// 注册 Vue.mixin() 实现混入
initMixin(Vue)
// 注册 Vue.extend() 基于传入的options返回一个组件的构造函数
initExtend(Vue)
// 注册 Vue.directive()、Vue.component()、Vue.filter()
initAssetRegisters(

本文深入解析Vue的初始化过程,涵盖静态成员与实例成员的初始化,以及Vue构造函数内部的_init方法工作原理。详细阐述了Vue如何设置响应式数据,渲染视图,包括编译template到render函数,以及mountComponent的核心作用。
最低0.47元/天 解锁文章
228

被折叠的 条评论
为什么被折叠?



