本篇文章讲的是Vue
的一个全局属性extend
,它的作用是返回一个Vue
对象的子类,先来讲它的原因是我们在创建VNode
对象时,如果当前元素是自定义组件,会用到它。
我们先来看一个使用它的例子:
<div id="mount-point"></div>
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
new Profile().$mount('#mount-point')
上面的代码,会在页面中输出如下内容:
<p>Walter White aka Heisenberg</p>
它是如何解析的呢?
该方法的定义是在src/core/global-api/extend.js
中的initExtend
方法内
export function initExtend (Vue: GlobalAPI) {
// 每个继承Vue的对象都有唯一的cid
Vue.cid = 0
let cid = 1
Vue.extend = function (extendOptions: Object): Function {
...
}
}
首先给Vue
添加了一个cid
,它的值为0,之后每次通过Vue.extend
创建的子类的cid
值依次递增。
我们按照刚才使用的例子来一步一步看看Vue.extend
都执行了哪些操作。
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
Super
保存了当前对象,这里是Vue
本身,SuperId
是Vue.cid
即0。extendOptions._Ctor
用于缓存构造函数,在Vue
源码中,暂未找到它的用途。
之后定义了对象Sub
,并给它添加了一系列的全局方法,我们看一下Sub
对象上,有哪些全局的属性:
Sub.cid
Sub.options
Sub.extend
Sub.mixin
Sub.use
Sub.component
Sub.directive
Sub.filter
// 新增
Sub.super // 指向父级构造函数
Sub.superOptions // 父级构造函数的options
Sub.extendOptions // 传入的extendOptions
Sub.sealedOptions // 保存定义Sub时,它的options值有哪些
它与Vue
的构造函数相比,增加了四个全局属性,同时也少了一些全局属性。
Vue
的全局属性见Vue-globals
Sub
上没有的属性包括:
Vue.version = '__VERSION__'
Vue.compile = compileToFunctions
Vue.config
Vue.util
Vue.set
Vue.delete
Vue.nextTick
当new Profile()
时,同样会调用this._init(options)
方法,我们在《从一个小栗子查看Vue的声明周期》中提到过,_init
方法会合并传入的options
和构造器上的options
属性,当时对resolveConstructorOptions
方法只是简单提了一下,这里我们再来看这个方法:
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
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
就是Profile
,也就是我们上面所说的Sub
。Ctor.super
即Vue
,因为Sub
上也有全局的extend
方法,所以可能会有Profile2 = Profile.extend({})
的请的情况,superOptions
递归调用了resolveConstructorOptions
来获得父级的options
。
如果Ctor
上保存的superOptions
与通过递归调用resolveConstructorOptions
获取到的options
不同,则说明父级构造器上options
属性值改变了,if (superOptions !== cachedSuperOptions)
里面的操作其实就是更新Ctor
上options
相关属性。该种情况的出现比如如下例子:
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
});
Vue.mixin({ data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}});
new Profile().$mount('#mount-point');
该例子可以正常运行,但是如果你注释掉if
块的内容,就无法运行了。页面中输出的文字就只剩下aka
了。这是因为Vue.mixin
执行时,会替换Vue.options
的值。
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
}
而我们new Profile
时,获取到Vue
上的options
的值还是旧的,所以没有正常渲染。
我们简单看一下更新的流程:
1、 Ctor.superOptions
指向最新的superOptions
2、 通过resolveModifiedOptions
方法获得修改的options
值。
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
let modified
const latest = Ctor.options
const sealed = Ctor.sealedOptions
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = dedupe(latest[key], sealed[key])
}
}
return modified
}
function dedupe (latest, sealed) {
// compare latest and sealed to ensure lifecycle hooks won't be duplicated
// between merges
if (Array.isArray(latest)) {
const res = []
sealed = Array.isArray(sealed) ? sealed : [sealed]
for (let i = 0; i < latest.length; i++) {
if (sealed.indexOf(latest[i]) < 0) {
res.push(latest[i])
}
}
return res
} else {
return latest
}
}
其实就是以options
为基础,把options
的值和sealedOptions
的值作比较,如果不同,若为数组,则合并数组,否则舍弃sealedOptions
的值。
3、如果modifiedOptions
值不为空,则合并到Ctor.extendOptions
4、更新Ctor.options
的值
以上基本就是Vue.extend
的主要过程,如果之后发现有落下的,会接着补充。