mergeOptions 函数(1)
先回顾下,在声明 Vue 对象构造函数是,直接调用 initMixin 函数,并把 Vue 作为参数:
initMixin(Vue)
initMixin 是个很简单的函数:把 Vue 作为参数,并在自己的原型上添加了个 ._init 方法。
function initMixin (Vue) {
Vue.prototype._init = function (options) {
[. . . .]
}
}
上篇文章里,我们分析了 Vue.prototype 上的 ._init 方法:
- 给
this设置一个辅助变量vm - 每当
._init方法被调用时,添加一个自增的变量.uid,并设置为 Vue 实例的属性 - 在特定条件下,增加一个性能检查。这个检查会返回一个时间戳,且在浏览器的输入缓冲性能处,有个基于
.uid的独特标记。 - 如果 Vue 对象构造函数中
options对象传给了,且options的属性.isComponent为true,那么就会调用initInternalComponent函数。
再看 else 语句,如果 options 未传递给 Vue 对象构造函数或者属性 .isComponent 为 false ,设置个 $options 属性,值为 mergeOptions 函数的返回结果:
[. . . .]
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
resolveConstructorOptions 函数
函数 resolveConstructorOptions 中的参数为 Vue 的实例,这个函数在代码里的其他地方已经声明过了:
function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
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 就是 constructor 的缩写。首先,设置了一个 options 变量,值为构造函数的一属性 Ctor.options 。接着,如果 Ctor.super 存在,设置一个 superOptions 变量,值为把 Ctor.super 作为参数,调用 resolveConstructorOptions 方法后的返回结果。
function resolveConstructorOptions (Ctor) {
let options = Ctor.options
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
[. . . .]
}
}
属性 super 在 initExtend 函数中定义,这个函数在其他地方(src/core/global-api/extend.js)中定义的。initExtend 把 Vue 作为参数,并定义了一个 extend 方法,作为继承类。
export function initExtend (Vue: GlobalAPI) {
/**
* Each instance constructor, including Vue, has a unique
* cid. This enables us to create wrapped "child
* constructors" for prototypal inheritance and cache them.
*/
Vue.cid = 0
let cid = 1
/**
* Class inheritance
*/
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name)
}
const Sub = function VueComponent (options) {
this._init(options)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
// For props and computed properties, we define the proxy getters on
// the Vue instances at extension time, on the extended prototype. This
// avoids Object.defineProperty calls for each instance created.
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}
// allow further extension/mixin/plugin usage
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// 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
}
}
后续的文档中,我们会分析 initExtend 函数。现阶段,知道 super 提供了简单的访问链以进行继承就足够了。
接着,为构造函数的属性 superOptions 设置了一个变量 cachedSuperOptions 。superOptions 也是定义在 Vue.extend 方法里。
正如注释所讲,superOptions 是从扩展(例如,当 Vue.extend 被调用时)中设置的 Super 继承 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
}
}
如果在扩展到实例化的过程中 options 变化了,设置构造函数的 superOptions 属性为变量 superOptions ,来处理新的 options 。
接着,函数 resolveModifiedOptions 检测是否有一些后期修改或附件选项。为此,变量 modifiedOptions 的值为把 Ctor 作为参数,调用 resolveModifiedOptions 方法后的返回结果。
function resolveConstructorOptions (Ctor) {
[. . . .]
const modifiedOptions = resolveModifiedOptions(Ctor)
[. . . .]
}
resolveModifiedOptions 函数
在代码里做这个检测,是为了修复这个 issue #4976。为了弄明白这里发生了什么,不妨看下相应的问题报告。“我发现在核心库里,会出现偶尔丢弃了为构造函数后期注入的 options 的一个问题。这意味着,如果我们使用 vue-hot-reload-api 或者 vue-loader ,在创建组件构造函数后,它们会给组件的 options 注入一些 options ,然后组件通过使用 $createElement 创建的构造函数来实例化,在 resolveComponentOptions 函数中那些注入的 options 会被丢弃。”
resolveModifiedOptions 把构造函数 Ctor 作为参数:
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
let modified
const latest = Ctor.options
const extended = Ctor.extendOptions
const sealed = Ctor.sealedOptions
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = dedupe(latest[key], extended[key], sealed[key])
}
}
return modified
}
resolveModifiedOptions 函数声明了一个 modified 变量。你可能会注意到,在函数的后面,modified 变量是函数的返回值。
resolveModifiedOptions 函数初始化3个变量:latest extended sealed 。属性 extendOptions 和 sealedOptions 都在 Vue.extend 方法中定义。
extendOptions 设置为传递给 Vue.extend 方法的任何参数。sealedOptions 设置为调用 extend 方法(而不是 Vue.extend 方法)后的返回值,参数为一个空对象和 Sub.options 。extend 方法在其他处(src/shared/util.js)定义的:
extend 工具函数
/**
* Mix properties into target object.
*/
export function extend (to: Object, _from: ?Object): Object {
for (const key in _from) {
to[key] = _from[key]
}
return to
}
这个函数接收两个参数: to 和 _from 。然后遍历 _from 的键,并把 _from 的键作为 to 的键。最后返回 to 。
了解过 extend 函数后,我们可以理解当表达式 Sub.sealedOptions = extend({}, Sub.options) 的意思了。extend 函数遍历 Sub.options 的所有键,并设置作为第一个参数的键。就我们的目的而言,重要的是要注意到,这发生在扩展 extension 时。
现在我们可以理解 resolveComponentOptions 中的 for 循环了:
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = dedupe(latest[key], extended[key], sealed[key])
}
}
遍历构造函数的属性 options 以检测它的每一项是否和 sealed 有不同——键在扩展时创建的。如果有任一键是不同的,这个函数就会创建一个 modified 对象,并会把的调用参数为 latest[key] extended[key]sealed[key]的dedupe方法后的结果赋值给modified[key]` 。
dedupe 函数定义在其他处,在下一章我们会分析下它。