Skip to content

Latest commit

 

History

History
196 lines (158 loc) · 6.27 KB

Vue.extend.md

File metadata and controls

196 lines (158 loc) · 6.27 KB

本篇文章讲的是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本身,SuperIdVue.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,也就是我们上面所说的SubCtor.superVue,因为Sub上也有全局的extend方法,所以可能会有Profile2 = Profile.extend({})的请的情况,superOptions递归调用了resolveConstructorOptions来获得父级的options

如果Ctor上保存的superOptions与通过递归调用resolveConstructorOptions获取到的options不同,则说明父级构造器上options属性值改变了,if (superOptions !== cachedSuperOptions)里面的操作其实就是更新Ctoroptions相关属性。该种情况的出现比如如下例子:

  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的主要过程,如果之后发现有落下的,会接着补充。