Skip to content

Vue 源码学习(三)- Vue.extend #10

@zhuping

Description

@zhuping

在上一篇构造函数部分,我们知道了在 global-api 添加全局 API 的时候,在 Vue 上增加了 Vue.extend 方法,因为考虑到后面讲解 initMixin 部分有涉及到这部分的内容,所以今天先来讲讲这个 Vue.extend 好了。

通过 Vue 的 extend 文档 我们知道,它是使用基础 Vue 构造器,可以用来创建一个“子类”。比如:

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

我们直接来看下代码:

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

下面我们来逐行分析下代码,extendOptions 为通过 Vue.extend 透传进去的 options 对象,Super 指向当前构造函数 Vue。初始给 Vue 添加一个 cid,它的值为0,之后每次通过 Vue.extend 创建的子类的 cid 值依次递增。extendOptions._Ctor 是用来缓存子类构造函数,如果已存在就直接返回。

在讲全局 API 的时候,我们有讲到过 Vue.component 这个方法,它是用来注册子组件的

// src/core/global-api/assets.js

export function initAssetRegisters(Vue: GlobalAPI) {
  ASSET_TYPES.forEach(type => {
    Vue[type] = function(
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      ...
      if (type === 'component' && isPlainObject(definition)) {
        definition.name = definition.name || id
        definition = this.options._base.extend(definition)
      }
    }  
  })
}

如果当 type = 'component'definition 是一个对象时,比如:

Vue.component('my-component-name', {  })

就会执行 this.options._base.extend(definition),相当于是 Vue.extend(definition),这里的 name 就是 my-components-name。所以在 Vue.extend 中下面这段代码是用来校验组件名称是否规范用的。

const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
  validateComponentName(name)
}

再下面就是继承部分了,创建 Sub 构造函数,同时把父类的原型赋值到 Sub.prototype 上。除了原型链的继承,还把父类的静态属性和方法也一并继承了下来:

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

最终返回子类。

小结

我们可以看下,现在子类 Sub 上已经包含了哪些属性和方法:

Sub.cid
Sub.options // 包含了父类的 options 和子类本身的 options
Sub.extend
Sub.mixin
Sub.use
Sub.component
Sub.directive
Sub.filter

// 新增
Sub.super = Super
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)

对比了上一篇中父类上列举的属性和方法,除了新增部分,Sub 上没有的属性包括:

Vue.config
Vue.util

Vue.set
Vue.delete
Vue.nextTick
Vue.observable

参考

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions