2

在Vue初始化中,$option属性来自于mergeOptions函数:

vm.$options = mergeOptions(
  resolveConstructorOptions(vm.constructor),
  options || {},
  vm
)

它接受3个参数,其中后面两个很好理解:一个是new Vue(options)时传进来的参数options;一个是初始化最开始就定义的变量const vm: Component = this,它指向了该Vue实例。那么这第一个参数是个啥玩意?这个函数返回了啥东西?

resolveConstructorOptions

这个函数源码在src/core/instance/init.js中的96行:

export 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
}

这个函数接受一个参数,就是实例的构造函数,即vm.constructor。通过上面的代码可以知道,他的实现逻辑分为两种,一种是构造函数上的super属性存在,则对其进行处理;如果super属性不存在,则直接返回构造函数上的option值。

super不存在

我们通过前面的初始化分析里可知,当new Vue时,vm.constructor里是不存在super属性的。这时候resolveConstructorOptions是直接返回的构造函数的option,还是通过上文的分析知道,这个构造函数的option是在initGlobalAPI里实现的:

 Vue.options = Object.create(null)
 ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
 })

 Vue.options._base = Vue

 extend(Vue.options.components, builtInComponents)

首先,我们把基础的构造函数(即new Vue里调用的那个)指定到options里的_base属性上,然后再加上ASSET_TYPES里的三个属性。

export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

最后一步,通过extend方法,把全局组件KeepAlive加入到options里的components上。
等等,这还没完。在前面的初始化分析里,我们看到在platforms/web/runtime/index里,我们还会安装平台相关的指令和组件。

// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

在web端我们安装的是v-modelv-show这两个指令和TransitionTransitionGroup这两个组件。
因此,在super不存在的情况下,我们return的option是这个样子的:
image.png

super存在

那么super在什么时候会存在呢?因为这resolveConstructorOptions是被export出去的,也就是说不只在init.js里使用了他。我们全局搜索一下resolveConstructorOptions,发现他在src/core/vdom/create-component.js也有用到:

export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }

  const baseCtor = context.$options._base

  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }

  ......

  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor)
  
  ......

}

也就是说我们在createComponent,即构造一个组件实例的时候,也会调用这个resolveConstructorOptions,这时候函数接收的Ctor来自于Ctor = baseCtor.extend(Ctor),而这个extend就是初始化时挂载到Vue构造函数上的extend方法:

  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

    ......
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)
    ......
    return Sub
  }

那么这个extend方法他到底做了啥?其实官网中已经做了解释:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
image.png
所以extend函数的作用就是返回一个子组件的构造函数,他通过Object.create(), prototype, constructor三者结合实现了子组件对基础Vue组件的继承。我们来看一下具体的流程:

  1. 通过const Super = this,把Super指向父类,即基础Vue构造函数。
  2. 定义了子组件的构造函数Sub。当我们new这个子组件时,就会执行这个构造函数Sub,而他最终执行的是this._init(options)。当然,这时候我们的Sub同志还是一个光杆司令,只有原生function对象的属性和方法,当然不会有_init。为了让Sub拥有_init方法,我们就开始了从Super上的继承。
  3. Sub.prototype = Object.create(Super.prototype),通过Object.create方法,将Super的原型作为自己原型的__proto__,实现原型继承。这时候,我们的Sub就会拥有Vue基础构造器的所有原型方法,包括Vue.prototype._init
  4. 当然,这时候还有个小问题,Sub.prototype.constructor指向的也是Super.prototype.constructor,也就是Vue基础构造函数function Vue()。这时候需要通过Sub.prototype.constructor = Sub把他指向我们子类自己的构造函数function VueComponent()

然后通过mergeOption等一系列骚操作完成子组件独有属性的挂载,最后返回了这个子类构造函数Sub。什么?为啥不用继承?当然是为了避免跟全局的污染啊,继承的其实都是公共的属性和方法,像data这种的当然不能通过继承实现。
那么,我们心心念念的super属性呢?其实就在这段代码里 Sub['super'] = Super,通过他把Vue基础构造器挂载到了子类构造函数的super属性上了。同时我们也通过Sub.superOptions = Super.options把Vue基础构造器的option选项挂载到了子类构造函数的superOptions属性上。
既然找到了super,那么Ctor = baseCtor.extend(Ctor)的这个Ctor中自然会有super属性,因此这时候执行的 resolveConstructorOptions(Ctor)就会走另外一段逻辑:

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;
            }
        }
    }
  1. 首先调用resolveConstructorOptions方法,把父类的options找出来,因为可能父类也有父类,因此这里是递归查找。他从最开始不存在super的基础构造函数function Vue()开始,进行递归,把上一级父类上最新的options更新到变量superOptions中。
  2. 然后把上面提到的,extend时父类的options,即 Ctor.superOptions赋值给cachedSuperOptions变量。
  3. 如果当这两个变量值不等时,就说明在extend之后,我们的父组件options悄悄咪咪的发生了变化,比如官网中这样比较粗暴的全局混入:image.png
  4. 那么既然父类可能被混入,我们这个子类当然也可能咯!我们通过resolveModifiedOptions这个函数,来判断现有的子类option和在extend中挂载到子类option中的sealedOptions(可以查看上面extend函数最后三段代码)是否一样,如果不一样说明肯定有什么骚操作在extend之后改变了子类option。
  5. 然后如果,子类自己或者父类在extend后options有变化,会通过Ctor.superOptions = superOptions;extend(Ctor.extendOptions, modifiedOptions);更新最新的值到superOptions和extendOptions
  6. 更新完后,最新的options值通过 options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);来获得。

通过上面的操作,可以确保resolveConstructorOptions返回的都是最新的Ctor.options值。

存在super的情况比较复杂,我们用一个简单的例子来拆解一下:

let Score = Vue.extend({
            template: '<p>{{firstName}} {{lastName}} : {{score}}</p>'
        });
        Vue.mixin({
            data() {
                return {
                    firstName: 'Walter',
                    lastName: 'White'
                };
            }
        });
        Score.mixin({ 
            data: function () {
                return {
                    score: '99'
                };
            }
        });
        new Score().$mount('#demo');
  1. 首先,因为Score是通过Vue.extend构造的子类,因此他的Ctor存在super属性image.png
  2. 然后,在extend的时候,父类Vue中时没有data属性的,即cachedSuperOptions中没有data

image.png
但是经过Vue.mixin的骚操作后,最新得到的superOptions是有data的
image.png
这时候因为两者不相等,就会进入if语句里面。

  1. 又因为Socre.mixin的骚操作后,resolveModifiedOptions这个函数发现Socre中的options又比sealedOptions多了dataimage.png

这时候的modifiedOptions就不是undefined了:
image.png
4.最后,执行 options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);,最新的options值通过mergeOptions这个函数得到
image.png

所以,总结一下resolveConstructorOptions函数的作用:返回类的构造函数上最新的options值。


FrankChencc
214 声望9 粉丝