vue系列---Vue组件化的实现原理(八)


1.1 全局注册组件 1.2 局部注册组件
  • 二:组件之间数据如何传递的呢? 1) props 2) $emit 3) 使用$ref实现通信 4) $attrs 和 $listeners 及 inheritAttrs 5) 理解 provide 和 inject 用法 6) 理解使用bus总线
  • 三:在vue源码中注册组件是如何实现的呢?
  • 在Vue中,组件是一个很强大的功能,组件可以扩展HTML元素,封装可重用的代码。比如在页面当中的某一个部分需要在多个场景中使用,那么我们可以将其抽出为一个组件来进行复用。组件可以大大提高了代码的复用率。

    所有的Vue组件都是Vue的实列,因此它可以接受Vue中的所有的生命周期钩子。Vue又分为全局注册和局部注册两种方式来注册组件。全局组件它可以在任何(根)实列中使用的组件,局部组件只能在某一实列中使用的组件。

    回到顶部回到顶部回到顶部回到顶部回到顶部回到顶部回到顶部回到顶部回到顶部

    三:在vue源码中注册组件是如何实现的呢?

    3.1 全局注册组件 

    上面已经介绍过, 全局注册组件有2种方式; 第一种方式是通过Vue.component 直接注册。第二种方式是通过Vue.extend来注册。

    Vue.component 注册组件

    比如如下代码:

    DOCTYPE html>
    <html>
    <head>
      <title>vue组件测试title>
      <meta charset="utf-8">
      <script type="text/javascript" src="https://cn.vuejs.org/js/vue.js">script>
    head>
    <body>
      <div id="app">
        
        <button-counter>button-counter><br/><br/>
      div>
      <script type="text/javascript">
        Vue.component('button-counter', {
          data: function() {
            return {
              count: 0
            }
          },
          template: ''
        });
        new Vue({
          el: '#app'
        });
      script>
    body>
    html>

    如上组件注册是通过 Vue.component来注册的, Vue注册组件初始化的时候, 首先会在 vue/src/core/global-api/index.js 初始化代码如下:

    import { initAssetRegisters } from './assets'
    initAssetRegisters(Vue);

    因此会调用 vue/src/core/global-api/assets.js 代码如下:

    /* @flow */
    
    import { ASSET_TYPES } from 'shared/constants'
    import { isPlainObject, validateComponentName } from '../util/index'
    
    export function initAssetRegisters (Vue: GlobalAPI) {
      /**
       * Create asset registration methods.
       */
      ASSET_TYPES.forEach(type => {
        Vue[type] = function (
          id: string,
          definition: Function | Object
        ): Function | Object | void {
          if (!definition) {
            return this.options[type + 's'][id]
          } else {
            /* istanbul ignore if */
            if (process.env.NODE_ENV !== 'production' && type === 'component') {
              validateComponentName(id)
            }
            if (type === 'component' && isPlainObject(definition)) {
              definition.name = definition.name || id
              definition = this.options._base.extend(definition)
            }
            if (type === 'directive' && typeof definition === 'function') {
              definition = { bind: definition, update: definition }
            }
            this.options[type + 's'][id] = definition
            return definition
          }
        }
      })
    }

    如上代码中的 'shared/constants' 中的代码在 vue/src/shared/constants.js 代码如下:

    export const SSR_ATTR = 'data-server-rendered'
    
    export const ASSET_TYPES = [
      'component',
      'directive',
      'filter'
    ]
    
    export const LIFECYCLE_HOOKS = [
      'beforeCreate',
      'created',
      'beforeMount',
      'mounted',
      'beforeUpdate',
      'updated',
      'beforeDestroy',
      'destroyed',
      'activated',
      'deactivated',
      'errorCaptured',
      'serverPrefetch'
    ]

    因此 ASSET_TYPES = ['component', 'directive', 'filter']; 然后上面代码遍历:

    ASSET_TYPES.forEach(type => {
      Vue[type] = function (
        id: string,
        definition: Function | Object
      ): Function | Object | void {
        if (!definition) {
          return this.options[type + 's'][id]
        } else {
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && type === 'component') {
            validateComponentName(id)
          }
          if (type === 'component' && isPlainObject(definition)) {
            definition.name = definition.name || id
            definition = this.options._base.extend(definition)
          }
          if (type === 'directive' && typeof definition === 'function') {
            definition = { bind: definition, update: definition }
          }
          this.options[type + 's'][id] = definition
          return definition
        }
      }
    });

    从上面源码中我们可知: Vue全局中挂载有 Vue['component'], Vue['directive'] 及 Vue['filter']; 有全局组件, 指令和过滤器。在Vue.component注册组件的时候, 我们是如下调用的:

    Vue.component('button-counter', {
      data: function() {
        return {
          count: 0
        }
      },
      template: ''
    });

    因此在源码中我们可以看到: 

    id = 'button-counter'; 
    definition = {
      template: '',
      data: function() {
        return {
          count: 0
        }
      }
    };

    如上代码, 首先我们判断如果 definition 未定义的话,就返回 this.options 中内的types 和id对应的值。this.options 有如下值:

    this.options = {
      base: function(Vue),
      components: {
        KeepAlive: {},
        Transition: {},
        TransitionGroup: {}
      },
      directives: {
        mode: {},
        show: {}
      },
      filters: {
    
      }
    };

    如上我们知道type的可取值分别为: 'component', 'directive', 'filter'; id为: 'button-counter'; 因此如果 definition 未定义的话, 就返回: return this.options[type + 's'][id]; 因此如果type为 'component' 的话, 那么就返回 this.options['components']['button-counter']; 从上面我们的 this.options 的值可知; this.options['components'] 的值为:

    this.options['components'] = {
        KeepAlive: {},
        Transition: {},
        TransitionGroup: {}
      };

    因此如果 definition 值为未定义的话, 则返回 return this.options['components']['button-counter']; 的值为 undefined;

    如果definition定义了的话, 如果不是正式环境的话, 就调用 validateComponentName(id); 方法, 该方法的作用是验证我们组件名的合法性; 该方法代码如下:

    // 验证组件名称的合法性
    function validateComponentName (name) {
      if (!new RegExp(("^[a-zA-Z][\\-\\.0-9_" + (unicodeRegExp.source) + "]*$")).test(name)) {
        warn(
          'Invalid component name: "' + name + '". Component names ' +
          'should conform to valid custom element name in html5 specification.'
        );
      }
      if (isBuiltInTag(name) || config.isReservedTag(name)) {
        warn(
          'Do not use built-in or reserved HTML elements as component ' +
          'id: ' + name
        );
      }
    }

    如果是component(组件)方法,并且definition是对象, 源码如下:

    if (type === 'component' && isPlainObject(definition)) {
      definition.name = definition.name || id = 'button-counter';
      definition = this.options._base.extend(definition)
    }

    我们可以打印下 this.options._base 的值如下:

    如上我们可以看到 this.options._base.extend 就是指向了 Vue.extend(definition); 作用是将定义的对象转成了构造器。

    Vue.extend 代码在 vue/src/core/global-api/extend.js中, 代码如下:

    /*
     @param {extendOptions} Object
     extendOptions = {
       name: 'button-counter',
       template: '',
       data: function() {
        return {
          count: 0
        }
      }
     };
     */
    Vue.cid = 0;
    var cid = 1;
    Vue.extend = function (extendOptions: Object): Function {
      extendOptions = extendOptions || {}
      const Super = this
      const SuperId = Super.cid
      // 如果组件已经被缓存到extendOptions, 则直接取出组件
      const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
      if (cachedCtors[SuperId]) {
        return cachedCtors[SuperId]
      }
      /*
        获取 extendOptions.name 因此 name = 'button-counter'; 
        如果有name属性值的话, 并且不是正式环境的话,验证下组件名称是否合法
       */
      const name = extendOptions.name || Super.options.name
      if (process.env.NODE_ENV !== 'production' && name) {
        validateComponentName(name)
      }
      
      const Sub = function VueComponent (options) {
        this._init(options)
      }
      /*
        将Vue原型上的方法挂载到 Sub.prototype 中。
        因此Sub的实列会继承了Vue原型中的所有属性和方法。
       */
      Sub.prototype = Object.create(Super.prototype)
      // Sub原型重新指向Sub构造函数
      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
    }

    如上代码中会调用 mergeOptions 函数, 该函数的作用是: 用于合并对象, 将两个对象合并成为一个。如上代码: 

    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    );

    如上函数代码,我们可以看到 mergeOptions 有两个参数分别为: Super.options 和 extendOptions。他们的值可以看如下所示:

    如上我们可以看到, Super.options 和 extendOptions 值分别为如下:

    Super.options = {
      _base: function Vue(options),
      components: {},
      directives: {},
      filters: {}
    };
    extendOptions = {
      name: 'button-counter',
      template: '',
      data: function() {
        return {
          count: 0
        }
      },
      _Ctor: {}
    };

    该mergeOptions函数的代码在 src/core/util/options.js 中, 基本代码如下:

    /* 
      参数 parent, child, 及 vm的值分别为如下:
      parent = {
        _base: function Vue(options),
        components: {},
        directives: {},
        filters: {}
      };
      child = {
        name: 'button-counter',
        template: '',
        data: function() {
          return {
            count: 0
          }
        },
        _Ctor: {}
      }
      vm: undefined
    */
    export function mergeOptions (
      parent: Object,
      child: Object,
      vm?: Component
    ): Object {
      if (process.env.NODE_ENV !== 'production') {
        checkComponents(child)
      }
    
      if (typeof child === 'function') {
        child = child.options
      }
    
      normalizeProps(child, vm)
      normalizeInject(child, vm)
      normalizeDirectives(child)
    
      // Apply extends and mixins on the child options,
      // but only if it is a raw options object that isn't
      // the result of another mergeOptions call.
      // Only merged options has the _base property.
      if (!child._base) {
        if (child.extends) {
          parent = mergeOptions(parent, child.extends, vm)
        }
        if (child.mixins) {
          for (let i = 0, l = child.mixins.length; i < l; i++) {
            parent = mergeOptions(parent, child.mixins[i], vm)
          }
        }
      }
    
      const options = {}
      let key
      for (key in parent) {
        mergeField(key)
      }
      for (key in child) {
        if (!hasOwn(parent, key)) {
          mergeField(key)
        }
      }
      function mergeField (key) {
        const strat = strats[key] || defaultStrat
        options[key] = strat(parent[key], child[key], vm, key)
      }
      return options
    }

    如上代码, 首先该函数接收3个参数, 分别为: parent, child, vm ,值分别如上注释所示。第三个参数是可选的, 在这里第三个参数值为undefined; 第三个参数vm的作用是: 会根据vm参数是实列化合并还是继承合并。从而会做不同的操作。

    首先源码从上往下执行, 会判断是否是正式环境, 如果不是正式环境, 会对组件名称进行合法性校验。如下基本代码:

    export function validateComponentName (name: string) {
      if (!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)) {
        warn(
          'Invalid component name: "' + name + '". Component names ' +
          'should conform to valid custom element name in html5 specification.'
        )
      }
      if (isBuiltInTag(name) || config.isReservedTag(name)) {
        warn(
          'Do not use built-in or reserved HTML elements as component ' +
          'id: ' + name
        )
      }
    }
    function checkComponents (options: Object) {
      for (const key in options.components) {
        validateComponentName(key)
      }
    }
    if (process.env.NODE_ENV !== 'production') {
      checkComponents(child)
    }

    接下来会判断传入的参数child是否为一个函数,如果是的话, 则获取它的options的值重新赋值给child。也就是说child的值可以是普通对象, 也可以是通过Vue.extend继承的子类构造函数或是Vue的构造函数。基本代码如下:

    if (typeof child === 'function') {
      child = child.options
    }

    接下来会执行如下三个函数:

    normalizeProps(child, vm)
    normalizeInject(child, vm)
    normalizeDirectives(child)

    它们的作用是使数据能规范化, 比如我们之前的组件之间的传递数据中的props或inject, 它既可以是字符串数组, 也可以是对象。指令directives既可以是一个函数, 也可以是对象。在vue源码中对外提供了便捷, 但是在代码内部做了相应的处理。 因此该三个函数的作用是将数据转换成对象的形式。

    normalizeProps 函数代码如下:

    /*
     @param {options} Object 
     options = {
       name: 'button-counter',
        template: '',
        data: function() {
          return {
            count: 0
          }
        },
        _Ctor: {}
     };
     vm = undefined
     */
    function normalizeProps (options: Object, vm: ?Component) {
      const props = options.props
      if (!props) return
      const res = {}
      let i, val, name
      if (Array.isArray(props)) {
        i = props.length
        while (i--) {
          val = props[i]
          if (typeof val === 'string') {
            name = camelize(val)
            res[name] = { type: null }
          } else if (process.env.NODE_ENV !== 'production') {
            warn('props must be strings when using array syntax.')
          }
        }
      } else if (isPlainObject(props)) {
        for (const key in props) {
          val = props[key]
          name = camelize(key)
          res[name] = isPlainObject(val)
            ? val
            : { type: val }
        }
      } else if (process.env.NODE_ENV !== 'production') {
        warn(
          `Invalid value for option "props": expected an Array or an Object, ` +
          `but got ${toRawType(props)}.`,
          vm
        )
      }
      options.props = res
    }

    该函数的作用对组件传递的 props 数据进行处理。在这里我们的props为undefined,因此会直接return, 但是我们之前父子组件之间的数据传递使用到了props, 比如如下代码:

    var childComponent = Vue.extend({
      template: '
    {{ content }}
    ', // 使用props接收父组件传递过来的数据 props: { content: { type: String, default: 'I am is childComponent' } } });

    因此如上代码的第一行: const props = options.props; 因此props的值为如下:

    props = {
      content: {
        type: String,
        default: 'I am is childComponent'
      }
    };

    如上props也可以是数组的形式, 比如 props = ['x-content', 'name']; 这样的形式, 因此在代码内部分了2种情况进行判断, 第一种是处理数组的情况, 第二种是处理对象的情况。

    首先是数组的情况, 如下代码:

    export function cached (fn: F): F {
      const cache = Object.create(null)
      return (function cachedFn (str: string) {
        const hit = cache[str]
        return hit || (cache[str] = fn(str))
      }: any)
    }
    
    const camelizeRE = /-(\w)/g;
    /*
     该函数的作用是把组件中的 '-' 字符中的第一个字母转为大写形式。
     比如如下代码:
     'a-b'.replace(/-(\w)/g, (_, c) => c ? c.toUpperCase() : ''); 
      最后打印出 'aB';
     */
    export const camelize = cached((str: string): string => {
      return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
    })
    
    const res = {}
    let i, val, name
    if (Array.isArray(props)) {
      i = props.length
      while (i--) {
        val = props[i]
        if (typeof val === 'string') {
          name = camelize(val)
          res[name] = { type: null }
        } else if (process.env.NODE_ENV !== 'production') {
          warn('props must be strings when using array syntax.')
        }
      }
    }

    我们可以假设props是数组, props = ['x-content', 'name']; 这样的值。 因此 i = props.length = 2; 因此就会进入while循环代码, 最后会转换成如下的形式:

    res = {
      'xContent': { type: null },
      'name': { type: null }
    };

    同理如果我们假设我们的props是一个对象形式的话, 比如值为如下:

    props: {
      'x-content': String,
      'name': Number
    };

    因此会执行else语句代码; 代码如下所示:

    const _toString = Object.prototype.toString;
    
    function isPlainObject (obj: any): boolean {
      return _toString.call(obj) === '[object Object]'
    }
    
    else if (isPlainObject(props)) {
      for (const key in props) {
        val = props[key]
        name = camelize(key)
        res[name] = isPlainObject(val)
          ? val
          : { type: val }
      }
    }

    因此最后 res的值变为如下:

    res = {
      'xContent': {
        type: function Number() { ... }
      },
      'name': {
        type: function String() { ... }
      }
    };

    当然如上代码, 如果某一个key本身是一个对象的话, 就直接返回该对象, 比如 props 值如下:

    props: {
      'x-content': String,
      'name': Number,
      'kk': {'name': 'kongzhi11'}
    }

    那么最后kk的键就不会进行转换, 最后返回的值res变为如下:

    res = {
      'xContent': {
        type: function Number() { ... }
      },
      'name': {
        type: function String() { ... }
      },
      'kk': {'name': 'kongzhi11'}
    };

    因此最后我们的child的值就变为如下值了:

    child = {
      name: 'button-counter',
      template: '',
      data: function() {
        return {
          count: 0
        }
      },
      _Ctor: {},
      props: {
        'xContent': {
          type: function Number() { ... }
        },
        'name': {
          type: function String() { ... }
        },
        'kk': {'name': 'kongzhi11'}
        }
      }
    };

    normalizeInject 函数, 该函数的作用一样是使数据能够规范化, 代码如下:

    function normalizeInject (options: Object, vm: ?Component) {
      const inject = options.inject
      if (!inject) return
      const normalized = options.inject = {}
      if (Array.isArray(inject)) {
        for (let i = 0; i < inject.length; i++) {
          normalized[inject[i]] = { from: inject[i] }
        }
      } else if (isPlainObject(inject)) {
        for (const key in inject) {
          const val = inject[key]
          normalized[key] = isPlainObject(val)
            ? extend({ from: key }, val)
            : { from: val }
        }
      } else if (process.env.NODE_ENV !== 'production') {
        warn(
          `Invalid value for option "inject": expected an Array or an Object, ` +
          `but got ${toRawType(inject)}.`,
          vm
        )
      }
    }

    同理, options的值可以为对象或数组。options值为如下:

    options = {
      name: 'button-counter',
      template: '',
      data: function() {
        return {
          count: 0
        }
      },
      inject: ['name'],
      _Ctor: {}
    };

    同理依次执行代码; const inject = options.inject = ['name'];

    1: inject数组情况下:

    inject是数组的话, 会进入if语句内, 代码如下所示:

    var normalized = {};
    
    if (Array.isArray(inject)) {
      for (let i = 0; i < inject.length; i++) {
        normalized[inject[i]] = { from: inject[i] }
      }
    }

    因此最后 normalized 的值变为如下:

    normalized = {
      'name': {
        from: 'name'
      }
    };

    因此child值继续变为如下值:

    child = {
      name: 'button-counter',
      template: '',
      data: function() {
        return {
          count: 0
        }
      },
      _Ctor: {},
      props: {
        'xContent': {
          type: function Number() { ... }
        },
        'name': {
          type: function String() { ... }
        },
        'kk': {'name': 'kongzhi11'}
        }
      },
      inject: {
        'name': {
          form: 'name'
        }
      }
    };

    2. inject为对象的情况下:

    比如现在options的值为如下:

    options = {
      name: 'button-counter',
      template: '',
      data: function() {
        return {
          count: 0
        }
      },
      inject: {
        foo: {
          from: 'bar',
          default: 'foo'
        }
      },
      _Ctor: {}
    };

    如上inject配置中的 from表示在可用的注入内容中搜索用的 key,default当然就是默认值。默认是 'foo', 现在我们把它重置为 'bar'. 因此就会执行else if语句代码,基本代码如下所示:

    /**
     * Mix properties into target object.
     */
    export function extend (to: Object, _from: ?Object): Object {
      for (const key in _from) {
        to[key] = _from[key]
      }
      return to
    }
    
    else if (isPlainObject(inject)) {
      for (const key in inject) {
        const val = inject[key]
        normalized[key] = isPlainObject(val)
          ? extend({ from: key }, val)
          : { from: val }
      }
    } 

    由上可知; inject值为如下:

    inject = {
      foo: {
        from: 'bar',
        default: 'foo'
      }
    };

    如上代码, 使用for in 遍历 inject对象。执行代码 const val = inject['foo'] = { from: 'bar', default: 'foo' }; 可以看到val是一个对象。因此会调用 extend函数方法, 该方法在代码 vue/src/shared/util.js 中。
    代码如下:

    /*
      @param {to}
      to = {
        from: 'foo'
      }
      @param {_from}
      _form = {
        from: 'bar',
        default: 'foo'
      }
     */
    export function extend (to: Object, _from: ?Object): Object {
      for (const key in _from) {
        to[key] = _from[key]
      }
      return to
    }

    如上执行代码后, 因此最后 normalized 值变为如下:

    normalized = {
      foo: {
        from: 'bar',
        default: 'foo'
      }
    };

    因此我们通过格式化 inject后,最后我们的child的值变为如下数据了:

    child = {
      name: 'button-counter',
      template: '',
      data: function() {
        return {
          count: 0
        }
      },
      _Ctor: {},
      props: {
        'xContent': {
          type: function Number() { ... }
        },
        'name': {
          type: function String() { ... }
        },
        'kk': {'name': 'kongzhi11'}
        }
      },
      inject: {
        'foo': {
          default: 'foo',
          from: 'bar'
        }
      }
    };

    现在我们继续执行 normalizeDirectives(child); 函数了。 该函数的代码在 vue/src/core/util/options.js中,代码如下:

    /*
     * Normalize raw function directives into object format.
     * 遍历对象, 如果key值对应的是函数。则把他修改成对象的形式。
     * 因此从下面的代码可以看出, 如果vue中只传递了函数的话, 就相当于这样的 {bind: func, unpdate: func}
     */
    function normalizeDirectives (options: Object) {
      const dirs = options.directives
      if (dirs) {
        for (const key in dirs) {
          const def = dirs[key]
          if (typeof def === 'function') {
            dirs[key] = { bind: def, update: def }
          }
        }
      }
    }

    现在我们再回到 vue/src/core/util/options.js中 export function mergeOptions () 函数中接下来的代码:

    export function mergeOptions (
      parent: Object,
      child: Object,
      vm?: Component) {
      : Object {
        // ...  代码省略
        if (!child._base) {
          if (child.extends) {
            parent = mergeOptions(parent, child.extends, vm)
          }
          if (child.mixins) {
            for (let i = 0, l = child.mixins.length; i < l; i++) {
              parent = mergeOptions(parent, child.mixins[i], vm)
            }
          }
        }
    
        const options = {}
        let key
        for (key in parent) {
          mergeField(key)
        }
        for (key in child) {
          if (!hasOwn(parent, key)) {
            mergeField(key)
          }
        }
        function mergeField (key) {
          const strat = strats[key] || defaultStrat
          options[key] = strat(parent[key], child[key], vm, key)
        }
        return options
      }
    }

    从上面可知, 我们的child的值为如下:

    child = {
      name: 'button-counter',
      template: '',
      data: function() {
        return {
          count: 0
        }
      },
      _Ctor: {},
      props: {
        'xContent': {
          type: function Number() { ... }
        },
        'name': {
          type: function String() { ... }
        },
        'kk': {'name': 'kongzhi11'}
        }
      },
      inject: {
        'foo': {
          default: 'foo',
          from: 'bar'
        }
      }
    };

    因此 child._base 为undefined, 只有合并过的选项才会有 child._base 的值。这里判断就是过滤掉已经合并过的对象。 因此会继续进入if语句代码判断是否有 child.extends 这个值,如果有该值, 会继续调用mergeOptions方法来对数据进行合并。最后会把结果赋值给parent。
    继续执行代码 child.mixins, 如果有该值的话, 比如 mixins = [xxx, yyy]; 这样的,因此就会遍历该数组,递归调用mergeOptions函数,最后把结果还是返回给parent。

    接着继续执行代码;

    // 定义一个空对象, 最后把结果返回给空对象
    const options = {}
    let key
    /*
     遍历parent, 然后调用下面的mergeField函数
     parent的值为如下:
     parent = {
       _base: function Vue(options),
      components: {},
      directives: {},
      filters: {}
     };
     因此就会把components, directives, filters 等值当作key传递给mergeField函数。
    */
    for (key in parent) {
      mergeField(key)
    }
    for (key in child) {
      if (!hasOwn(parent, key)) {
        mergeField(key)
      }
    }
    /*
     该函数主要的作用是通过key获取到对应的合并策略函数, 然后执行合并, 然后把结果赋值给options[key下面的starts的值,在源码中
     的初始化中已经定义了该值为如下:
     const strats = config.optionMergeStrategies;
     starts = {
       activated: func,
       beforeCreate: func,
       beforeDestroy: func,
       beforeMount: func,
       beforeUpdate: func,
       components: func,
       computed: func,
       created: func,
       data: func,
       deactivated: func,
       destroyed: func,
       directives: func,
       filters: func
       ......
     };
     如下代码: const strat = strats[key] || defaultStrat; 
     就能获取到对应中的函数, 比如key为 'components', 
     因此 start = starts['components'] = function mergeAssets(){};
    */
    function mergeField (key) {
      const strat = strats[key] || defaultStrat
      options[key] = strat(parent[key], child[key], vm, key)
    }
    return options

    mergeAssets函数代码如下:

    function mergeAssets (
      parentVal: ?Object,
      childVal: ?Object,
      vm?: Component,
      key: string
    ): Object {
      const res = Object.create(parentVal || null)
      if (childVal) {
        process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
        return extend(res, childVal)
      } else {
        return res
      }
    }

    最后返回的options的值变为如下了:

    options = {
      name: 'button-counter',
      template: '',
      data: function() {
        return {
          count: 0
        }
      },
      _Ctor: {},
      props: {
        'xContent': {
          type: function Number() { ... }
        },
        'name': {
          type: function String() { ... }
        },
        'kk': {'name': 'kongzhi11'}
        }
      },
      inject: {
        'foo': {
          default: 'foo',
          from: 'bar'
        }
      },
      components: {},
      directives: {},
      filters: {}
    };

    因此我们再回到代码 vue/src/core/global-api/extend.js 代码中的Vue.extend函数,如下代码:

    Vue.extend = function (extendOptions: Object): Function {
      //......
      /*
       Sub.options的值 就是上面options的返回值
      */
      Sub.options = mergeOptions(
        Super.options,
        extendOptions
      )
      Sub['super'] = Super;
      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.options值为如下:

    Sub.options = {
      name: 'button-counter',
      template: '',
      data: function() {
        return {
          count: 0
        }
      },
      _Ctor: {},
      props: {
        'xContent': {
          type: function Number() { ... }
        },
        'name': {
          type: function String() { ... }
        },
        'kk': {'name': 'kongzhi11'}
        }
      },
      inject: {
        'foo': {
          default: 'foo',
          from: 'bar'
        }
      },
      components: {},
      directives: {},
      filters: {}
    };

    因此执行代码:

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

    从上面的数据我们可以知道 Sub.options.props 有该值的,因此会调用 initProps 函数。代码如下:

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

    因此 const props = Comp.options.props; 

    即 props = {
      'xContent': {
        type: function Number() { ... }
      },
      'name': {
        type: function String() { ... }
      },
      'kk': {'name': 'kongzhi11'}
      }
    }

    使用for in 循环该props对象。最后调用 proxy 函数, 该函数的作用是使用 Object.defineProperty来监听对象属性值的变化。
    该proxy函数代码如下所示:

    该proxy函数代码在 vue/src/core/instance/state.js 中,代码如下所示:

    const sharedPropertyDefinition = {
      enumerable: true,
      configurable: true,
      get: noop,
      set: noop
    }
    
    export function proxy (target: Object, sourceKey: string, key: string) {
      sharedPropertyDefinition.get = function proxyGetter () {
        return this[sourceKey][key]
      }
      sharedPropertyDefinition.set = function proxySetter (val) {
        this[sourceKey][key] = val
      }
      Object.defineProperty(target, key, sharedPropertyDefinition)
    }

    继续执行代码如下:

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

    判断是否有computed选项, 如果有的话,就调用 initComputed(Sub); 该函数代码在 vue/src/core/instance/state.js; 该代码源码先不分析, 会有对应的章节分析的。最后代码一直到最后, 会返回Sub对象, 该对象值就变为如下了:

    Sub = {
      cid: 1,
      component: func,
      directive: func,
      extend: func,
      extendOptions: {
        name: 'button-counter',
        template: '',
        data: function() {
          return {
            count: 0
          }
        },
        _Ctor: {},
        props: {
          'xContent': {
            type: function Number() { ... }
          },
          'name': {
            type: function String() { ... }
          },
          'kk': {'name': 'kongzhi11'}
          }
        },
        inject: {
          'foo': {
            default: 'foo',
            from: 'bar'
          }
        }
      },
      filter,func,
      mixin: func,
      options: {
        name: 'button-counter',
        template: '',
        data: function() {
          return {
            count: 0
          }
        },
        _Ctor: {},
        props: {
          'xContent': {
            type: function Number() { ... }
          },
          'name': {
            type: function String() { ... }
          },
          'kk': {'name': 'kongzhi11'}
          }
        },
        inject: {
          'foo': {
            default: 'foo',
            from: 'bar'
          }
        },
        components: {},
        directives: {},
        filters: {},
        components: button-counter: f VueComponent,
        _base: f Vue()
        ......
      }
    };

    注意:在代码中会有如下一句代码; 就是会把我们的组件 'button-counter' 放到 Sub.options.components 组件中。

    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }

    如上代码执行完成 及 返回完成后,我们再回到 vue/src/core/global-api/assets.js 代码中看接下来的代码:

    /* @flow */
    
    import { ASSET_TYPES } from 'shared/constants'
    import { isPlainObject, validateComponentName } from '../util/index'
    
    export function initAssetRegisters (Vue: GlobalAPI) {
      /**
       * Create asset registration methods.
       */
      ASSET_TYPES.forEach(type => {
        Vue[type] = function (
          id: string,
          definition: Function | Object
        ): Function | Object | void {
          if (!definition) {
            return this.options[type + 's'][id]
          } else {
            /* istanbul ignore if */
            if (process.env.NODE_ENV !== 'production' && type === 'component') {
              validateComponentName(id)
            }
            if (type === 'component' && isPlainObject(definition)) {
              definition.name = definition.name || id
              definition = this.options._base.extend(definition)
            }
            if (type === 'directive' && typeof definition === 'function') {
              definition = { bind: definition, update: definition }
            }
            this.options[type + 's'][id] = definition
            return definition
          }
        }
      })
    }

    因此 最后代码: this.options[type + 's'][id] = definition; 

    this.options = {
      components: {
        KeepAlive: {},
        Transition: {},
        TransitionGroup: {},
        button-counter: ? VueComponent(options){}
      },
      directives: {},
      filters: {},
      base: f Vue(){}
    };
    
    this.options[type + 's'][id] = this.options['components']['button-counter'] = f VueComponent(options);

    最后我们返回 definition 该Vue的实列。即definition的值为如下:

    definition = ? VueComponent (options) {
      this._init(options);
    }

    最后我们就会调用 new Vue() 方法来渲染整个生命周期函数了,因此button-counter组件就会被注册上可以调用了。

    相关