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

上面已经介绍过, 全局注册组件有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: 'You clicked me {{ count }} times.'
});

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

id = 'button-counter';
definition = {
  template: 'You clicked me {{ count }} times.',
  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 的值如下:

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

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

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

/*
 @param {extendOptions} Object
 extendOptions = {
   name: 'button-counter',
   template: 'You clicked me {{ count }} times.',
   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: 'You clicked me {{ count }} times.',
  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: 'You clicked me {{ count }} times.',
    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: 'You clicked me {{ count }} times.',
    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: 'You clicked me {{ count }} times.',
  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: 'You clicked me {{ count }} times.',
  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: 'You clicked me {{ count }} times.',
  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: 'You clicked me {{ count }} times.',
  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: 'You clicked me {{ count }} times.',
  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: 'You clicked me {{ count }} times.',
  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: 'You clicked me {{ count }} times.',
  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: 'You clicked me {{ count }} times.',
  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: 'You clicked me {{ count }} times.',
    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: 'You clicked me {{ count }} times.',
    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组件就会被注册上可以调用了。

Original: https://www.cnblogs.com/tugenhua0707/p/11761280.html
Author: 龙恩0707
Title: vue系列—Vue组件化的实现原理(八)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/553788/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球