什麼是 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
}