有兴趣了解我们的下一本书吗?了解更多关于 使用 React 构建大型 JavaScript Web 应用程序

设计模式

Mixin 模式

一个 **mixin** 是一个对象,我们可以用它来为另一个对象或类添加可复用的功能,而无需使用继承。我们不能单独使用 mixin:它们的唯一目的是添加功能到对象或类,而无需继承。

假设我们的应用程序需要创建多个狗。但是,我们创建的基本狗除了name属性之外没有任何属性。

class Dog {
  constructor(name) {
    this.name = name;
  }
}

一只狗应该能够做的不仅仅是拥有一个名字。它应该能够吠叫,摇尾巴,玩耍!我们可以创建一个 mixin 来为我们提供barkwagTailplay属性,而不是直接将这些属性添加到Dog中。

const dogFunctionality = {
  bark: () => console.log("Woof!"),
  wagTail: () => console.log("Wagging my tail!"),
  play: () => console.log("Playing!"),
};

我们可以使用Object.assign方法将dogFunctionalitymixin添加到Dog原型。此方法允许我们将属性添加到目标对象:在本例中为Dog.prototypeDog的每个新实例都可以访问dogFunctionality的属性,因为它们被添加到Dog的原型中!

class Dog {
  constructor(name) {
    this.name = name;
  }
}

const dogFunctionality = {
  bark: () => console.log("Woof!"),
  wagTail: () => console.log("Wagging my tail!"),
  play: () => console.log("Playing!"),
};

Object.assign(Dog.prototype, dogFunctionality);

让我们创建我们的第一个宠物pet1,名叫Daisy。由于我们刚刚将dogFunctionalitymixin添加到Dog的原型中,Daisy应该能够走路、摇尾巴和玩耍!

const pet1 = new Dog("Daisy");

pet1.name; // Daisy
pet1.bark(); // Woof!
pet1.play(); // Playing!

完美!Mixin 使我们能够轻松地为类或对象添加自定义功能,而无需使用继承。


虽然我们可以在没有继承的情况下使用 mixin 添加功能,但 mixin 本身可以使用继承!

大多数哺乳动物(除了海豚……可能还有其他一些)也能走路和睡觉。狗是哺乳动物,应该能够走路和睡觉!

让我们创建一个animalFunctionalitymixin,它添加了walksleep属性。

const animalFunctionality = {
  walk: () => console.log("Walking!"),
  sleep: () => console.log("Sleeping!"),
};

我们可以使用Object.assign将这些属性添加到dogFunctionality原型。在本例中,目标对象是dogFunctionality

const animalFunctionality = {
  walk: () => console.log("Walking!"),
  sleep: () => console.log("Sleeping!"),
};

const dogFunctionality = {
  bark: () => console.log("Woof!"),
  wagTail: () => console.log("Wagging my tail!"),
  play: () => console.log("Playing!"),
  walk() {
    super.walk();
  },
  sleep() {
    super.sleep();
  },
};

Object.assign(dogFunctionality, animalFunctionality);
Object.assign(Dog.prototype, dogFunctionality);

完美!任何新的Dog实例现在也可以访问walksleep方法。

index.js
1class Dog {
2 constructor(name) {
3 this.name = name;
4 }
5}
6
7const animalFunctionality = {
8 walk: () => console.log("Walking!"),
9 sleep: () => console.log("Sleeping!")
10};
11
12const dogFunctionality = {
13 __proto__: animalFunctionality,
14 bark: () => console.log("Woof!"),
15 wagTail: () => console.log("Wagging my tail!"),
16 play: () => console.log("Playing!"),
17 walk() {
18 super.walk();
19 },
20 sleep() {
21 super.sleep();
22 }
23};
24
25Object.assign(Dog.prototype, dogFunctionality);
26
27const pet1 = new Dog("Daisy");
28
29console.log(pet1.name);
30pet1.bark();
31pet1.play();
32pet1.walk();
33pet1.sleep();

在浏览器环境中,Window接口上就可见 mixin 的现实世界例子。Window对象实现其许多属性来自WindowOrWorkerGlobalScopeWindowEventHandlersmixin,它们允许我们访问诸如setTimeoutsetIntervalindexedDBisSecureContext之类的属性。

由于它是一个 mixin,因此仅用于添加功能到对象,因此您将无法创建类型为WindowOrWorkerGlobalScope的对象。

index.js
1window.indexedDB.open("toDoList");
2
3window.addEventListener("beforeunload", event => {
4 event.preventDefault();
5 event.returnValue = "";
6});
7
8window.onbeforeunload = function() {
9 console.log("Unloading!");
10};
11
12console.log(
13 "From WindowEventHandlers mixin: onbeforeunload",
14 window.onbeforeunload
15);
16
17console.log(
18 "From WindowOrWorkerGlobalScope mixin: isSecureContext",
19 window.isSecureContext
20);
21
22console.log(
23 "WindowEventHandlers itself is undefined",
24 window.WindowEventHandlers
25);
26
27console.log(
28 "WindowOrWorkerGlobalScope itself is undefined",
29 window.WindowOrWorkerGlobalScope
30);

React(ES6 之前)

在引入 ES6 类之前,mixin 通常用于为 React 组件添加功能。React 团队不鼓励使用 mixin,因为它很容易为组件添加不必要的复杂性,使其难以维护和重用。React 团队鼓励使用高阶组件,现在通常可以用 Hooks 替换。


Mixin 允许我们通过将功能注入对象的原型来轻松地为对象添加功能,而无需继承。修改对象的原型被视为不良做法,因为它会导致原型污染以及对函数来源的不确定性。


参考资料