(全文2005字,大概会花费25-35min)
Vue实例初始化选项初合并 & 选项规范化校验
1.Vue实例初始化选项mix初合并(详细合并后续文章会提及,如果需要查看合并策略,请绕行文章——)
合并之后会挂载到实例的$options
属性中,可以在实例中通过this.$options
访问
var uid$3 = 0;
function initMixin (Vue) {
Vue.prototype._init = function (options) {
var vm = this;
// a uid 记录实例化多少个vue对象
vm._uid = uid$3++;
...
// a flag to avoid this being observed
vm._isVue = true;
// merge options - 选项合并,将合并值赋给实例的$options属性
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// 内部组件选项需要特殊处理。 - internal component options needs special treatment.
initInternalComponent(vm, options);
} else {
// Vue实例不是组件,会执行mergeOptions方法
// 将两个选项对象合并为一个新对象。
// 用于实例化和继承的核心实用程序。
vm.$options = mergeOptions(
// 返回Vue构造函数自身的配置项
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
...
};
}
通过研究resolveConstructorOptions
函数,发现:
function resolveConstructorOptions (Ctor) {
var options = Ctor.options;
// 有super属性,说明Vue.extend()构建的子类(下文会介绍)
if (Ctor.super) {
var superOptions = resolveConstructorOptions(Ctor.super);
// Vue构造函数上的options,如directives,filters,...
var 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)
var 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
}
注意:有super属性,说明Vue.extend()
构建的子类,会为Ctor
添加一个super属性,指向其父类构造器
Vue.extend = function (extendOptions: Object): Function {
...
Sub['super'] = Super
...
}
其中,Ctor
是基础Vue
构造器,通过打印superOtions
,结构为下:
{
"components": {},
"directives": {},
"filters": {},
"beforeCreate": [],
"beforeMount": [],
"beforeDestroy": [],
"destroyed": []
}
我们看mergeOptions
的部分:
/**
* Merge two option objects into a new one.
* Core utility used in both instantiation and inheritance.
*/
function mergeOptions (
parent,
child,
vm
) {
{ // components校验
checkComponents(child);
}
if (typeof child === 'function') {
child = child.options;
}
// 规范化 props,inject,directives的校验和规范化
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) {
// 当传入的options里有mixin/extends属性时,再次调用mergeOptions方法合并mixins和extends里的内容到实例的构造函数options上(即parent options)比如下文 例1的情况
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm);
}
if (child.mixins) {
for (var i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm);
}
}
}
// 核心部分
var options = {};
var key;
for (key in parent) {
mergeField(key);
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key);
}
}
function mergeField (key) {
// 拿到各个选择指定的选项配置,如果没有则用默认的配置
var strat = strats[key] || defaultStrat;
// 执行合并策略
options[key] = strat(parent[key], child[key], vm, key);
}
return options
}
例1:会把传入的mounted, created钩子处理函数,还有methods方法提出来和parent options做合并处理。
const childComponent = Vue.component('child', {
...
mixins: [myMixin],
extends: myComponent
...
})
const myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin')
}
}
}
const myComponent = {
mounted: function () {
this.yes()
},
methods: {
yes: function () {
console.log('yes from mixin')
}
}
}
为了更近异步理解,引用某大佬的合并流程图说明:
**选项合并中存在不清楚使用者传递了哪些配置选项,是否符合规范达到合并配置的要求。因此每个选项的书写规则需要严格限定,原则上不允许用户脱离规则外来传递选项。**所以在合并选项之前,很大的一部分工作是对选项的校验。其中components,prop,inject,directive
等都是检验的重点。
2.选项校验–components(checkComponents
)
代码:
/**
* unicode letters used for parsing html tags, component names and property paths.
* using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname
* skipping \u10000-\uEFFFF due to it freezing up PhantomJS
*/
// 用于解析 html 标签、组件名称和属性路径的 unicode 字母。使用 https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname,跳过 \u10000-\uEFFFF 因为它冻结了 PhantomJS
// ps:这里的PhantomJS是一个基于webkit的JavaScript API。它使用QtWebKit作为它核心浏览器的功能,使用webkit来编译解释执行JavaScript代码。亦称为“无头浏览器”
var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/;
/**
* Validate component names
*/
function checkComponents (options) {
for (var key in options.components) {
validateComponentName(key);
}
}
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.'
);
}
// 主要是防止自定义组件使用 HTML 内置,如svg等标签,及Vue自身定义的组件名,如slot, component,
if (isBuiltInTag(name) || config.isReservedTag(name)) {
warn(
'Do not use built-in or reserved HTML elements as component ' +
'id: ' + name
);
}
}
3.选项校验–props(normalizeProps
)
引用Vue2.x
官方原话:
类型:
Array<string> | Object
详细:
props 可以是数组或对象,用于接收来自父组件的数据。props 可以是简单的数组,或者使用对象作为替代,对象允许配置高级选项,如类型检测、自定义验证和设置默认值。
来自:https://cn.vuejs.org/v2/api/#props
代码:
/**
* Ensure all props option syntax are normalized into the
* Object-based format.
*/
function normalizeProps (options, vm) {
var props = options.props;
if (!props) { return }
var res = {};
var i, val, name;
// Array<string> | Object
if (Array.isArray(props)) { // 数组
i = props.length;
while (i--) {
val = props[i];
// 如果数组的某一项为字符串的话
if (typeof val === 'string') {
// 名字会被规范处理(驼峰式)
name = camelize(val);
// res添加属性,type默认为null
res[name] = { type: null };
// eg:如果传入['first-prop','second-prop'],会被转化为: {firstProp: {type: null}, secondProp: {type: null}}
} else {
warn('props must be strings when using array syntax.');
}
}
} else if (isPlainObject(props)) { // 对象
for (var key in props) {
// val赋值
val = props[key];
// 驼峰名字格式化
name = camelize(key);
/*
* 传入 props: { name: String } => { name: { type: String }}
* 传入props: { name: { type: String,required: true} } =>
* { name: { type: String, reuired: true } }
*/
res[name] = isPlainObject(val)
? val
: { type: val };
}
} else {
// 非数组,非对象,传递非法
warn(
"Invalid value for option \"props\": expected an Array or an Object, " +
"but got " + (toRawType(props)) + ".",
vm
);
}
options.props = res;
}
综上,通过此规范化校验,
props
Array<string> | Object
经过名称驼峰规范化等一系列处理,最终都会被转换对象的形式,尤大佬yyds = = !
4.选项校验–inject (normalizeInject
)
引用Vue2.x
官方原话:
provide/inject
依赖注入
类型:
- provide:
Object | () => Object
- inject:
Array<string> | { [key: string]: string | Symbol | Object }
详细:
provide
选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property
inject
选项应该是
- 一个字符串数组,或
- 一个对象,对象的 key 是本地的绑定名,value 是:
- 在可用的注入内容中搜索用的 key (字符串或 Symbol),或
- 一个对象,该对象的:
from
property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)default
property 是降级情况下使用的 value来自:https://cn.vuejs.org/v2/api/#provide-inject
如果不太了解provide/inject的用法,可访问https://cn.vuejs.org/v2/guide/components-edge-cases.html#%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5
/**
* Normalize all injections into Object-based format
*/
function normalizeInject (options, vm) {
// 缓存inject对象,做中间变量
var inject = options.inject;
if (!inject) { return }
// 将其置空,且保证normalized与options.inject指向同一个对象
var normalized = options.inject = {};
if (Array.isArray(inject)) {
for (var i = 0; i < inject.length; i++) {
normalized[inject[i]] = { from: inject[i] };
}
} else if (isPlainObject(inject)) {
for (var key in inject) {
var val = inject[key];
normalized[key] = isPlainObject(val)
? extend({ from: key }, val)
: { from: val };
}
} else {
warn(
"Invalid value for option \"inject\": expected an Array or an Object, " +
"but got " + (toRawType(inject)) + ".",
vm
);
}
}
综上,通过此规范化校验,
inject
最终被转化为对象的形式与props
类似
5.选项校验–directives(normalizeDirectives
)
类型: Object
代码:
/**
* Normalize raw function directives into object format.
*/
function normalizeDirectives (options) {
var dirs = options.directives;
if (dirs) {
for (var key in dirs) {
var def$$1 = dirs[key];
if (typeof def$$1 === 'function') {
dirs[key] = { bind: def$$1, update: def$$1 };
}
}
}
}
通读代码,Vue
允许我们使用自定义指令,并提供以下5类钩子
我们可以使用以下的方式去校验:
{
directives: {
'demo': function (el,binding) {
console.log(binding.value.color) // => "white"
}
}
}
通过结果,对
directives
进行规范化时,针对函数的写法会将行为赋予bind,update
钩子,并回调
Thanks♪(・ω・)ノ