js设计模式系列篇:混入(Mixin)模式

2,455 阅读2分钟

定义

在js中,我们可以将Mixin看作是一种通过扩展收集功能的方式。它非常适合js的对象原型,我们可以从不止一个Mixin中分享功能来提升代码的复用性,降低重复度

实现

function Car (model = null, color = '') {
    this.model = model
    this.color = color    
}

function CarMove () {}

CarMove.prototype.run = function () {
    console.log('car is running')
}

CarMove.prototype.stop = function () {
    console.log('car has stoped')
}

以上的代码中,我们分别把Car的属性和Car的方法提到了两个构造函数中,真实开发中当然不建议这么做,这么写只是为了展示Mixin模式的用法。

现在要做的就是编写Mixin函数了,请看以下代码

function mixin (origin, source, ...rest) {
    const restLen = rest.length
    if (restLen) {
        for (let i = 0; i < restLen; i++) {
            origin.prototype[rest[i]] = source.prototype[rest[i]]
        }
    } else {
        for (let method in source.prototype) {
            if (!Reflect.has(origin.prototype, method)) {
                origin.prototype[method] = source.prototype[method]
            }
        }
    }
}

Mixin函数的职责是将source对象中的方法混入到origin对象中去。有同学会想直接用Object.assign不就可以了,但是该函数与Object.assign有两点不同。

第一点是该函数可以指定需要混入source对象中的哪些属性,这在第一个判断语句中体现。如果用了3个或多个参数调用,表明指定了属性名,则会只混入指定的属性,剩下的不做处理。

第二点是优先级。在Object.assign中,如果源对象和目标对象同时含有某个属性,则源对象中的属性会覆盖目标对象中的属性,如下

const person1 = {
    name: 'foo',
    gender: 'male'
}

const person2 = {
    name: 'bar',
    age: 18
}

Object.assign(person1, person2)

console.log(person1)

以上代码的输出结果如图

image.png

可以看到,person2的name属性覆盖了person1的name属性,而在我们写的mixin函数中,源对象属性的优先级更高,这点可以从函数中第二个if语句看出来。

测试mixin函数

mixin(Car, CarMove)

console.log(Car.prototype)

输出

image.png

指定属性名

mixin(Car, CarMove, 'run')

console.log(Car.prototype)

输出

image.png

可以看出mixin函数运行正常

总结

Mixin模式有助于提高函数复用,减少系统代码的重复度。当一个程序在各个对象中共享属性和行为时,我们可以考虑使用Mixin模式。但Mixin模式也有自己的缺点,有些人认为将功能注入对象原型中的操作会导致原型污染和函数起源方面的不确定性等方面的问题,所以在使用时需要衡量利弊来做出选择。