文章篇幅较长, 建议花整块时间阅读分析。 另外由于篇幅过长, 本文分三篇文章产出, 便于大家理解与阅读。
在上一篇文章分析中,我们分析了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函数
解析当前实例构造函数上的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
}
####我们先看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
####再看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函数。
终于熬完了合并选项的分析(三篇文章), 每天抽出睡觉的时间来啃这块, 非常不容易,促进自己成长, 希望也能给小可爱们带去一些启发, 如果有地方觉得说的不清楚的欢迎在下方评论区提问,讨论~
看到这里的小可爱们, 可顺手点个赞鼓励我继续创作, 创作不易,一起学习, 早日实现财富自由~
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 爱钻研的果冻 原文链接:https://juejin.im/post/6932285432281432071