Vue源码阅读(29):Vue.extend 源码解析

 我的开源库:

最好先看下我的上篇文章,上篇文章的内容在本篇文章中能够用到,点击这里

 Vue.extend API 的官方文档点击这里

Vue.extend 是 Vue 中很重要的一个方法,虽然在平时的开发中很少用到它,但是在 Vue 源码内部,extend 方法却很重要。为什么说这个方法很重要呢?是因为在 Vue 中,组件的本质就是通过 extend 方法创建出来的 Vue 构造函数的子级构造函数。我们知道 extend 方法的参数是一个组件选项对象,extend 方法的内部会创建一个 Sub 函数并最终返回,这个 Sub 函数就是上面所说的子级构造函数,这个 Sub 函数的 options 属性保存着组件选项对象中的数据,当通过这个 Sub 函数实例化组件实例对象的时候,保存在 Sub.options 对象中能够描述该组件的数据便会被整形转移到组件实例对象中(vm.$options 属性中)至此,组件的 Vue 实例就创建出来了,并且该组件实例对象中还保存着被处理过的组件选项对象数据。

extend 方法的作用和重要性就讲到这里,接下来开始解析 extend 方法的源码,该方法定义在 src/core/global-api/extend.js 文件中。

1,extend 方法总览

首先总览一下 extend 方法,接下来进行一步步的解析。

Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    // Super = this = 父级构造器
    const Super = this
    // 拿到父级构造器的 cid
    const SuperId = Super.cid
    // extendOptions._Ctor 对象用于缓存这一个 extendOptions 对象已经创建了的构造函数
    // 缓存的 key 是父级构造器的 cid。
    // extendOptions + cid 能够唯一确定某一个子类构造器
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
      // 如果 extendOptions 已经和这一个父级构造器生成了子级构造器的话,
      // 在这里,直接 return 缓存中的子级构造器
      return cachedCtors[SuperId]
    }

    // 组件 name 校验
    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production') {
      // 非生产环境下,如果不合规范的话,打印出警报
      if (!/^[a-zA-Z][\w-]*$/.test(name)) {
        warn(
          'Invalid component name: "' + name + '". Component names ' +
          'can only contain alphanumeric characters and the hyphen, ' +
          'and must start with a letter.'
        )
      }
    }

    // 定义子构造函数 Sub
    const Sub = function VueComponent (options) {
      this._init(options)
    }
    // 将父级构造函数的原型对象赋值给 Sub.prototype
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    // 赋值自增长的 cid
    Sub.cid = cid++
    // 下面是实现继承的关键,使用 mergeOptions 方法将 父级的options 和 extendOptions 进行合并
    // 并将结果赋值给 Sub 子构造函数的 options 属性上
    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)
    }

    // 赋值全局的静态方法
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // ASSET_TYPES = [ 'component', 'directive', 'filter' ]
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }

    // 在 Sub 函数上面保存:(1)父级的 options;(2)当前拓展的 extendOptions;(3)上面两个 options 合并后的 options
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)

    // 缓存创建的 Sub 子构造器
    cachedCtors[SuperId] = Sub
    return Sub
  }

2,缓存处理

Vue.extend = function (extendOptions: Object): Function {
  extendOptions = extendOptions || {}
  // Super = this = 父级构造器
  const Super = this
  // 拿到父级构造器的 cid
  const SuperId = Super.cid
  // extendOptions._Ctor 对象用于缓存这一个 extendOptions 对象已经创建了的构造函数
  // 缓存的 key 是父级构造器的 cid。
  // extendOptions + cid 能够唯一确定某一个子类构造器
  const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
  if (cachedCtors[SuperId]) {
    // 如果 extendOptions 已经和这一个父级构造器生成了子级构造器的话,
    // 在这里,直接 return 缓存中的子级构造器
    return cachedCtors[SuperId]
  }

  ......
  ......

  // 缓存创建的 Sub 子构造器
  cachedCtors[SuperId] = Sub
  return Sub
}

extendOptions 对象中的 _Ctor 属性用于缓存通过 extendOptions 选项对象创建出的子级构造函数。_Ctor 属性是一个对象,该对象的 key 是父级构造函数的 cid,每个 Vue 构造函数都会有一个唯一标识 cid,cid 是一个递增的整数,_Ctor 对象的 value 是 Vue 构造函数,这个函数是通过当前的 extendOptions 对象和当前的父级构造函数借助 extend 方法创建出来的,所以某个子级 Vue 构造函数能够通过 extendOptions + SuperId 唯一标识。

在源码中,通过 if (cachedCtors[SuperId]) 判断 cachedCtors 对象有没有缓存当前想要创建的子级构造函数,如果当前想要创建的子级构造函数已经被创建过一次,则在这里直接返回 cachedCtors[SuperId] 即可,相同的子级构造函数不用重复创建,提高性能。

3,校验组件名称

在非生产环境下,对组件的名称进行校验,校验的规则是只能包含字母、数字和连字符,并且必须以字母开头,如果不满足要求的话,则打印出警告信息。

Vue.extend = function (extendOptions: Object): Function {
  ......
  ......

  // 组件 name 校验
  const name = extendOptions.name || Super.options.name
  if (process.env.NODE_ENV !== 'production') {
    // 非生产环境下,如果不合规范的话,打印出警报
    if (!/^[a-zA-Z][\w-]*$/.test(name)) {
      warn(
        'Invalid component name: "' + name + '". Component names ' +
        'can only contain alphanumeric characters and the hyphen, ' +
        'and must start with a letter.'
      )
    }
  }

  ......
  ......
}

4,定义子级构造函数 Sub

Vue.extend = function (extendOptions: Object): Function {
  ......
  ......

  // 定义子级构造函数 Sub
  const Sub = function VueComponent (options) {
    this._init(options)
  }

  ......
  ......
}

定义子级构造函数 Sub,函数体是 this._init(options),Vue 构造函数的函数体也是这样,保持一致即可。

Vue 构造函数。

function Vue (options) {
  // 如果当前的环境不是生产环境,并且当前命名空间中的 this 不是 Vue 的实例的话,
  // 发出警告,Vue 必须通过 new Vue({}) 使用,而不是把 Vue 当做函数使用
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // 执行 vm 原型上的 _init 方法,该方法在 initMixin 方法中定义
  this._init(options)
}

5,赋值 Sub 的原型对象

Vue.extend = function (extendOptions: Object): Function {
  ......
  ......

  // 将父级构造函数的原型对象赋值给 Sub.prototype
  Sub.prototype = Object.create(Super.prototype)
  Sub.prototype.constructor = Sub

  ......
  ......
}

将 Super.prototype 对象赋值给 Sub.prototype,这里借助原型链,使得 Sub 构造函数的实例可以访问到定义在原型链中的原型方法,原型链中的原型方法如下所示。

// 下面函数的作用是:往 Vue 的原型上写入原型函数,这些函数是给 Vue 的实例使用的
// 写入 vm._init
initMixin(Vue)
// 写入 vm.$set、vm.$delete、vm.$watch
stateMixin(Vue)
// 写入 vm.$on、vm.$once、vm.$off、vm.$emit
eventsMixin(Vue)
// 写入 vm._update、vm.$forceUpdate、vm.$destroy
lifecycleMixin(Vue)
// 写入 vm.$nextTick、vm._render
renderMixin(Vue)

6,合并构造函数的 options

Vue.extend = function (extendOptions: Object): Function {
  ......
  ......

  // 赋值自增长的 cid
  Sub.cid = cid++
  // 下面是实现继承的关键,使用 mergeOptions 方法将 父级的options 和 extendOptions 进行合并
  // 并将结果赋值给 Sub 子构造函数的 options 属性上
  Sub.options = mergeOptions(
    Super.options,
    extendOptions
  )
  Sub['super'] = Super

  ......
  ......
}

这段代码最重要的是合并 Super.options 和 extendOptions 到 Sub.options,这使得 Sub 构造函数的实例能够使用到父级构造函数的 options 和当前要拓展的 options,mergeOptions 方法的具体解释可以看我的这篇文章

7,处理 options.props 和 options.computed

Vue.extend = function (extendOptions: Object): Function {
  ......
  ......

  if (Sub.options.props) {
    initProps(Sub)
  }
  if (Sub.options.computed) {
    initComputed(Sub)
  }

  ......
  ......
}

function initProps (Comp) {
  const props = Comp.options.props
  for (const key in props) {
    proxy(Comp.prototype, `_props`, key)
  }
}

function initComputed (Comp) {
  const computed = Comp.options.computed
  for (const key in computed) {
    defineComputed(Comp.prototype, key, computed[key])
  }
}

options 中定义的 props 和 computed 属性,Vue 的处理方法是将它们定义到子级构造函数的原型对象中,这样做的好处是不用在每个创建出的实例上重复定义 props 和 computed,实例通过原型链访问定义在原型对象中的 props 和 computed 即可。

8,赋值全局的静态方法

Vue.extend = function (extendOptions: Object): Function {
  ......
  ......

  // 赋值全局的静态方法
  Sub.extend = Super.extend
  Sub.mixin = Super.mixin
  Sub.use = Super.use

  // ASSET_TYPES = [ 'component', 'directive', 'filter' ]
  ASSET_TYPES.forEach(function (type) {
    Sub[type] = Super[type]
  })

  ......
  ......
}

将父级构造函数上面的一些全局静态方法赋值到 Sub 子级构造函数上,这些静态方法通过 initGlobalAPI 方法已经被赋值到 Vue 构造函数上了,相关源码如下。

export function initGlobalAPI (Vue: GlobalAPI) {
  // 初始化 Vue.use()
  initUse(Vue)
  // 初始化 Vue.mixin()
  initMixin(Vue)
  // 初始化 Vue.extend()
  initExtend(Vue)
  // 初始化 Vue.component()、Vue.directive()、Vue.filter(),用于向 Vue 中注册资源
  initAssetRegisters(Vue)
}

9,在子级构造函数上定义一些 extend 特有的属性

Vue.extend = function (extendOptions: Object): Function {
  ......
  ......

  // 在 Sub 函数上面保存:
  //   (1)父级的 options;
  //   (2)当前拓展的 extendOptions;
  //   (3)上面两个 options 合并后的 options
  Sub.superOptions = Super.options
  Sub.extendOptions = extendOptions
  Sub.sealedOptions = extend({}, Sub.options)

  ......
  ......
}

10,缓存 Sub 函数并 return 出去

将创建出来的 Sub 函数缓存到 cachedCtors 对象中,并且在最后 return Sub 函数。

Vue.extend = function (extendOptions: Object): Function {
  ......
  ......

  // 缓存创建的 Sub 子构造器
  cachedCtors[SuperId] = Sub
  return Sub
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值