混入Mixin是Vue中非常常用的一个功能,也非常灵活,可以大大减少Vue组件中的重复功能,提高可维护性。
先看下使用方法:
// 定义一个混入对象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 定义一个使用混入对象的组件
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"
Mixin的优势在于灵活的配置项合并,可以根据业务需求以恰当的方式进行合并,如组件选项合并,全局混入,及自定义合并策略等等。
注意官方教程上有这么几句话:
别小看上面上面几点提示,可能关键时候会直接影响到程序性能。分别看看这几句话在源码上如何实现的。
先看下全局混入如何实现的:
function initGlobalAPI(Vue){
//...此前代码省略
initMixin$1(Vue);
//...此后代码省略
}
initGlobalAPI是全局函数,也是全局API的入口,其中就包含了全局混入。
function initMixin$1(Vue) {
//全局混入
Vue.mixin = function (mixin) {
this.options = mergeOptions(this.options, mixin);
return this;
};
}
由此可见核心方法在于mergeOptions,那mergeOptions干了些什么:
/**
这里主要干了两件事:
1、检查和序列化选项;
2、合并选项
*/
function mergeOptions(parent, child, vm) {
//检查是否是合法的组件
{
checkComponents(child);
}
if (typeof child === "function") {
child = child.options;
}
normalizeProps(child, vm);
normalizeInject(child, vm);
normalizeDirectives(child);
//这里的_base是Vue对象
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm);
}
//mixins是数组,遍历多个混入
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);
}
}
//注意这里的strats对象
function mergeField(key) {
var strat = strats[key] || defaultStrat;//合并策略
options[key] = strat(parent[key], child[key], vm, key);
}
return options;
}
上面代码看起来挺简单,不就是合并对象吗,我们看下上面要注意的strats对象是什么就知道了。
var strats = config.optionMergeStrategies;
strats是一个全局变量,用来重写父选项和子选项的合并规则。,它的属性包括组件的所有配置项。
//返回合并后的data
strats.data = function (parentVal, childVal, vm) {
if (!vm) {
if (childVal && typeof childVal !== "function") {
warn('The "data" option should be a function ' + "that returns a per-instance value in component " + "definitions.", vm);
return parentVal;
}
return mergeDataOrFn(parentVal, childVal);
}
return mergeDataOrFn(parentVal, childVal, vm);
};
function mergeDataOrFn(parentVal, childVal, vm) {
if (!vm) {
// Vue.extend合并,都必须是函数
if (!childVal) {
return parentVal;
}
if (!parentVal) {
return childVal;
}
return function mergedDataFn() {
return mergeData(typeof childVal === "function" ? childVal.call(this, this) : childVal, typeof parentVal === "function" ? parentVal.call(this, this) : parentVal);
};
} else {
return function mergedInstanceDataFn() {
// instance merge
var instanceData = typeof childVal === "function" ? childVal.call(vm, vm) : childVal;
var defaultData = typeof parentVal === "function" ? parentVal.call(vm, vm) : parentVal;
if (instanceData) {
return mergeData(instanceData, defaultData);
} else {
return defaultData;
}
};
}
}
//这里就干了一件事,递归合并data
function mergeData(to, from) {
if (!from) {
return to;
}
var key, toVal, fromVal;
var keys = hasSymbol ? Reflect.ownKeys(from) : Object.keys(from);
for (var i = 0; i < keys.length; i++) {
key = keys[i];
// in case the object is already observed...
if (key === "__ob__") {
continue;
}
toVal = to[key];
fromVal = from[key];
if (!hasOwn(to, key)) {
set(to, key, fromVal);
} else if (toVal !== fromVal && isPlainObject(toVal) && isPlainObject(fromVal)) {
mergeData(toVal, fromVal);
}
}
return to;
}
//合并props,methods,inject,computed
strats.props = strats.methods = strats.inject = strats.computed = function (parentVal, childVal, vm, key) {
if (childVal && "development" !== "production") {
assertObjectType(key, childVal, vm);
}
if (!parentVal) {
return childVal;
}
var ret = Object.create(null);
extend(ret, parentVal);//注意这里是创建了新对象,然后将父值合并到新对象
if (childVal) {
extend(ret, childVal);//所以是子组件覆盖或拓展父组件属性或方法
}
return ret;
};
//合并生命周期钩子函数
LIFECYCLE_HOOKS.forEach(function (hook) {
strats[hook] = mergeHook;
});
function mergeHook(parentVal, childVal) {
var res = childVal ? (parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal]) : parentVal;
return res ? dedupeHooks(res) : res;
}
//合并watch,注意不是覆盖,而是作为数组进行合并
strats.watch = function (parentVal, childVal, vm, key) {
// work around Firefox's Object.prototype.watch...
if (parentVal === nativeWatch) {
parentVal = undefined;
}
if (childVal === nativeWatch) {
childVal = undefined;
}
/* istanbul ignore if */
if (!childVal) {
return Object.create(parentVal || null);
}
{
assertObjectType(key, childVal, vm);
}
if (!parentVal) {
return childVal;
}
var ret = {};
extend(ret, parentVal);
for (var key$1 in childVal) {
var parent = ret[key$1];
var child = childVal[key$1];
if (parent && !Array.isArray(parent)) {
parent = [parent];
}
ret[key$1] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child];
}
return ret;
};
看到这我们应该明白了上面所说的第2、3点,钩子函数实际是存储在一个数组队列
中,这里是父组件钩子函数concat
子组件钩子函数,所以同名钩子函数都会执行,先执行父组件钩子函数,再执行子组件钩子函数。
所以在实际业务中用到全局mixin时候,我们会经常发现有些方法被执行了多次,先后顺序是不一样的。因此也就不难理解前文提到的第4点,谨慎使用全局mixin,因为它会在所有的组件中都会执行,一旦组件非常多就会导致严重的性能问题。举个例子,通常我们会把一个页面分成若干组件,如果一个列表有100个item组件,那么全局混入中的方法至少执行100次以上,如果其中有比较耗性能的方法,那么就会扩大100倍以上。所以当遇到有严重性能问题的时候,不妨看看是不是全局混入导致的。
小结一下
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!