上面已经介绍过, 全局注册组件有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 的值如下:
如上我们可以看到 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/
转载文章受原作者版权保护。转载请注明原作者出处!