什么是 Mixin 模式?
Mixin 模式属于结构型设计模式。正如字面意思,就是为了在一个类中能够混入另一个类的某些方法,以较低的复杂性达到复用的目的。甚至可以用来间接实现多继承。
实例
在 JS 中,我们利用对象原型来实现 Mixin。
下面是一个全局的混入方法:
注意:由于 class 中的方法是不可枚举的,但通过 Object.getOwnPropertyNames 可以返回一个数组包含对象自身拥有的可枚举和不可枚举属性。
/*
* @desc 全局混入方法
* @receivingClass object 接受混入方法的类名
* @givingClass object 提供混入方法的类名
* @func array 混入方法数组
*/
function mixin(receivingClass, givingClass, ...func) {
if (!func.length) {
// 由于class中的方法是不可遍历的,
// 需要使用 Object.getOwnPropertyNames 获取全部属性
func = Object.getOwnPropertyNames(givingClass.prototype)
}
func.forEach(item => {
// 已有的属性方法,不进行覆盖
if (!receivingClass.prototype[item]) {
receivingClass.prototype[item] = givingClass.prototype[item]
}
}
)
}
我们来试用下混入效果。
class SayWhat {
sayNo() {
console.log('oh! No!')
}
sayHi() {
console.log('Hi, my bro!')
}
}
class MyWord {
sayLoveMe() {
console.log('You are my sweet!')
}
sayMe() {
console.log('I am ZKK.')
}
}
// 将 MyWord 中的 sayLoveMe 方法混入 SayWhat 类中。
mixin(SayWhat, MyWord, ['sayLoveMe'])
const sayWhat = new SayWhat()
sayWhat.sayHi() // => 'Hi, my bro!'
sayWhat.sayLoveMe() // => 'You are my sweet!'
// 如果不传入方法数组,将遍历 MyWord 原型上的所有属性到 SayWhat 原型上。
// 有点像继承了
mixin(SayWhat, MyWord)
sayWhat.sayMe() // => I am ZKK.
优缺点
优点
有助于减少系统中的重复功能及增加函数复用。当一个应用程序可能需要在各种对象实例中共享行为时,我们可以通过在 Mixin 中维持这种共享功能并专注于仅实现系统中真正不同的功能,来轻松避免任何重复。
缺点
有些人认为将功能注入对象原型中会导致原型污染和函数起源方面的不确定性。
扩展
Vue.js 框架中也有 Mixin 模式的应用,那就看看 Vue.mixin 的实现。
主要调用了 mergeOptions
这个辅助函数。
这个函数主要做了这几件事:
- 规范化选项
- 优先合并 extends 和 mixins 的选项。
- 合并其他
有兴趣的可以详细查看源代码,了解合并策略。
// src/core/global-api/mixin.js
import { mergeOptions } from '../util/index'
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
// src/core/util/options.js
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
...
// 规范化props
normalizeProps(child, vm)
// 规范化inject
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.
// 未合并的options不带有_base
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
}