鄢栋,微医前端技术部前端工程师。有志成为一名全栈开发工程师甚至架构师,路漫漫,吾求索。 生活中通过健身释放压力,思考问题。
文章篇幅较长, 建议花整块时间阅读分析。 另外由于篇幅过长, 本文分三篇文章产出, 便于大家理解与阅读。
前言
在上一篇文章分析中,我们分析了 Vue 的整体的运行机制, 其中, 我们知道 Vue 在初始化的时候调用了 init() 函数:
在 instance.js 文件中, 调用了 initMixin()方法。然后我们找到 init.js 中, 对 initMixin 函数的定义, 中间有这么一段代码:
// merge options
if (options && options._isComponent) { // 如果当前 Vue 实例是组件,执行 initInternalComponent
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options) // 该方法主要就是为 vm.$options 添加一些属性
} else {
// 否则就是实例化 Vue 对象,调用 mergeOptions
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
这段代码是合并选项, 那么到底是如何进行合并选项 options 呢?
首先我们来看直接实例化 Vue, 也就是:
// 否则就是实例化 Vue 对象,调用 mergeOptions
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
可以看到, 调用了mergeOptions(参数 1, 参数 2, 参数 3)函数
- 参数 1: resolveConstructorOptions(vm.constructor);
- 参数 2:options || {};
- 参数 3: vm
参数 2, 是我们创建 Vue 实例的时候传入的 options 参数 3, Vue 自身的实例
所以, 主要看参数 1: resolveConstructorOptions(vm.constructor)函数。
本篇幅就来介绍, vue 是如何解析当前实例构造器上的 options 的resolveConstructorOptions 函数。
resolveConstructorOptions
解析当前实例构造函数上的 options
/**
* @param {*} Ctor: vm.constructor
* 这个方法要分成两种情况来说明
* 第一种是 Ctor 是基础 Vue 构造器的情况
* 另一种是 Ctor 是通过 Vue.extend 方法扩展的情况。
*/
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
// 有 super 属性,说明 Ctor 是 Vue.extend 构建的子类
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions // Vue 构造函数上的 options
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
new Vue()构造子类
我们先看 Ctor 是基础 Vue 构造器的情况, 即通过 new Vue()构造子类
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
// 有 super 属性,说明 Ctor 是 Vue.extend 构建的子类
...
return options
}
Ctor 其实就是 Vue 构造器,Ctor.options 就是 Vue 构造函数上的 options, 这个时候直接返回了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>new Vue</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{message}}
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
created: function (){
console.log('this.constructor.options ', this.constructor.options)
}
})
</script>
</body>
</html>
我们打印 Vue 构造器上的 options 对象:
可以看到基础构造器上有components, directives, filters, _base四个基础属性, 这些属性是从哪里定义的呢?
既然我们是初始化整个框架,那么一定时需要框架项目能够运行, 因此我们找到 package.json 文件, 找到运行命令 scripts:
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
然后定位到 scripts/config.js, 找到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
},
可以看到入口文件是:web/entry-runtime-with-compiler.js
在 platforms/web/entry-runtime-with-compiler.js 中, 我们看到这么一行代码:
import Vue from './runtime/index'
在该文件中:
import Vue from 'core/index'
...
import platformDirectives from './directives/index'
import platformComponents from './components/index'
...
// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)
关于 Vue.options 的设置就是这么几行代码, 然后我们分别代开 platformDirectives、platformComponents 文件
platformDirectives 文件导出了 Vue 的全局指令 v-model, v-show
platformComponents 文件导出了 Vue 的全局动画组件 Transition, TransitionGroup 组件。
结合上面我们打印出的 vm.constructor.options 的截图
我们看到,除了上面两个文件定义的 directives 中的 model, show 以及 components 中的 Transition, TransitionGroup 以外, components 中的KeepAlive 组件还有filters, _base属性我们没有看到。
我们再回到 core/index 文件入口(第一篇文章中, 找到 instance.js 文件也是从这里进入的), 看到:
import Vue from './instance/index' // 第一篇文章的入口
import { initGlobalAPI } from './global-api/index'
...
initGlobalAPI(Vue)
...
进入到 global-api/index.js, 搜索 Vue.options:
import { ASSET_TYPES } from 'shared/constants'
import builtInComponents from '../components/index'
...
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
extend(Vue.options.components, builtInComponents)
// shared/constants
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
// ../components/index
import KeepAlive from './keep-alive'
export default {
KeepAlive
}
因此, 我们就找到了filters,_base和components 中的 KeepAlive。Vue 构造函数的 options 对象就很清晰了。
下面, 总结一下寻找基础 Vue 构造函数 options 选项的路径:
- 1、new Vue({})实例化
- 2、打印出
vm.constructor.options, 发现有components,directives,filters,_base四个属性 - 3、既然是项目初始化, 找到 package,json 文件, 找到项目启动命令: npm run dev
- 4、根据 dev 命令行定位的 scripts/config 文件, 找到了入口文件
web/entry-runtime-with-compiler.js - 5、通过该入口文件中的
import Vue from './runtime/index', 我们首先找到了 components 中的Transition、TransitionGroup组件;找到了 directives 属性中的model和show指令 - 6、再通过该文件
import Vue from './runtime/index', 发现import Vue from 'core/index' - 7、回到入口文件
core/index, 找到initGlobalApi, 进入global-api/index.js文件 - 8、搜索 Vue.options, 找到了剩下的 components 中的
keepAlive 组件以及filters和_base属性 经过上面寻找路径, 我们最终找全了 Vue 构造器的最初的 options 选项:components,directives,filters,_base
Vue.extend 构造子类
再看 Ctor.super, 即通过 Vue.extend 构造子类
// Vue.extend 函数在实例上加了一个 super 属性
Vue.extend = function (extendOptions: Object): Function {
...
Sub['super'] = Super
...
}
/**
* @param {*} Ctor: Ctor.super
* 这个方法要分成两种情况来说明
* 第一种是 Ctor 是基础 Vue 构造器的情况
* 另一种是 Ctor 是通过 Vue.extend 方法扩展的情况。
*/
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options // 相当于 vm.constructor.options
// 有 super 属性,说明 Ctor 是 Vue.extend 构建的子类
if (Ctor.super) { // 相当于 vm.constructor.super
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions // Vue 构造函数上的 options
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
我们看下通过 Vue.extend()创建实例,举个例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>new Vue</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app2"></div>
<script>
var Profile = Vue.extend({
template: '<p>123</p>',
})
console.log('new profile super ', new Profile().constructor.super)
console.log('new profile superOptions', new Profile().constructor.superOptions)
console.log('new profile constructor ', new Profile().constructor)
console.log('new profile constructor options', new Profile().constructor.options)
new Profile().$mount('#app2')
</script>
</body>
</html>
我们找到 Vue.extend 函数的定义:
export function initExtend (Vue: GlobalAPI) {
...
/**
* Class inheritance
*/
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
...
const Sub = function VueComponent (options) {
this._init(options) // 这里执行了 Vue 构造器上的_init 函数
}
Sub.prototype = Object.create(Super.prototype) // 继承 Super, 也就是 Vue
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
...
// create asset registers, so extended classes
// can have their private assets too.
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type]
})
// enable recursive self-lookup
if (name) {
Sub.options.components[name] = Sub
}
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// cache constructor
cachedCtors[SuperId] = Sub
return Sub
}
}
可以看到,在 Vue.extend 函数中, Sub(VueComponent)继承了 Super(Vue), 然后在 Sub 构造器上添加了
- options: 融合了 Super.options 和 extendOptions(我们在 Vue.extend()中传入的选项)
- super 属性: 指向 Super(Vue)
- superOptions 属性: Super.options
- extendOptions 属性: 传入的参数
- sealedOptions 属性:Sub.options(见第一个)
这个时候, 我们打印一下super, superOptions, extendOptions, vm.constructor, vm.constructor.options
从通过 Vue.extend 创建实例的图中, 可以看到
vm.constructor.super的值其实就是 Vue 构造器。而vm.constructor.superOptions其实是继承自基础 Vue 构造器上的选项。extendOptions是我们传入的参数。- vm.constructor 是子类的构造器
VueComponent, - 而
vm.constructor.options的值,除了继承的四个基础属性:components,directives,filters,_base以外, 还有我们自定义的template属性。 superOptions + extendOptions = vm.constructor.options(vm.$options)
所以:
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions // Vue 构造函数上的 options
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options. 将自身的 options 替换成最新的
Ctor.superOptions = superOptions
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor)
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
这段代码的意思就是:将传入的选项以及父级 Vue 构造器上的选项进行合并返回 options。
当然,中间还有一个检查选项是否发生改变的函数 resolveModifiedOptions, 就不深入分析了。
小结
所以,resolveConstructorOptions函数的作用是:解析当前实例构造函数上的 options
在 Vue.extend 方法中涉及到 ES5 原型继承的知识, 如果不熟悉的话线下可以翻阅一下书籍。
到此我们就详细介绍了 vue 解析当前实例构造器上的 options 选项的原理, 下一篇我们继续介绍外层合并选项函数 mergeOptions 函数。
终于熬完了合并选项的分析(三篇文章), 每天抽出睡觉的时间来啃这块, 非常不容易,促进自己成长, 希望也能给小可爱们带去一些启发, 如果有地方觉得说的不清楚的欢迎在下方评论区提问,讨论~
看到这里的小可爱们, 可顺手点个赞鼓励我继续创作, 创作不易,一起学习, 早日实现财富自由~