使用 JavaScript Mixins 的优点
什么是 Mixin?
在开始使用某样东西之前,我们需要了解它是什么以及我们可以用它做什么。
什么是 Mixin?
mixin 是一个抽象子类;即,可以应用于不同超类以创建相关的修改类系列的子类定义。
- Gilad Bracha 和 William Cook,基于 Mixin 的继承
以日志记录为例。假设你有 3 个页面
- 红色的
- 绿色的
- 蓝色的
              +----------+
              |   Page   |
              +----------+
                |  |  |
     +----------+  |  +-----------+
     |             |              |
+---------+ +-----------+ +----------+
| PageRed | | PageGreen | | PageBlue |
+----+----+ +-----------+ +----------+
class Page {}
class PageRed extends Page {}
class PageGreen extends Page {}
class PageBlue extends Page {}
现在,我们希望每当有人访问 Page Red 时都进行记录。
 为了实现这一点,我们扩展了 Page Red,并创建了一个 Logged Page Red。
              +----------+
              |   Page   |
              +-+--+--+--+
                |  |  |
     +----------+  |  +-----------+
     |             |              |
+----+----+  +-----+-----+  +-----+----+
| PageRed |  | PageGreen |  | PageBlue |
+----+----+  +-----------+  +----------+
     |
+----+----+
| Logged  |
| PageRed |
+---------+
class Page {}
class PageRed extends Page {}
class PageGreen extends Page {}
class PageBlue extends Page {}
class LoggedPagRed extends PageRed {}
如果我们想开始记录 PageGreen 的日志,我们会遇到一个问题:
- 我们不能把逻辑放进去,Page因为蓝色不应该被记录
- 我们无法重复使用其中的逻辑,Logged PageGreen因为我们无法从两个来源扩展(即使可以,也意味着红色和绿色中的信息相互冲突)
我们能做的就是把它放在一个“外部”的地方,然后写下来,这样它就可以“混入”了。
               +----------+                +----------+
               |   Page   |                | Logging* |
               +-+--+--+--+                +----------+
                 |  |  |
      +----------+  |  +-----------+
      |             |              |
+-----+----+  +-----+-----+  +-----+----+
| PageRed  |  | PageGreen |  | PageBlue |
|  with    |  |   with    |  +----------+
| Logging* |  |  Logging* |
+----------+  +-----------+
// defining the Mixin
export const LoggingMixin = superclass =>
  class LoggingMixin extends superclass {
    // logging logic
  };
class Page {}
// applying a Mixin
class PageRed extends LoggingMixin(Page) {}
class PageGreen extends LoggingMixin(Page) {}
class PageBlue extends Page {}
通过这种方法,我们可以将逻辑提取到单独的代码片段中,以便在需要时使用。
有关更深入的技术解释,请阅读《Real Mixins with JavaScript Classes》。
为什么需要对 Mixins 进行重复数据删除?
现在我们希望所有日志记录都记录到红、绿、蓝三个页面。
 很简单——因为我们现在可以在页面本身上应用 LoggingMixin 了。
               +----------+               +----------+
               |   Page   |               | Logging* |
               |   with   |               +----------+
               | Logging* |
               +-+--+--+--+
                 |  |  |
      +----------+  |  +-----------+
      |             |              |
+-----+----+  +-----+-----+  +-----+----+
| PageRed  |  | PageGreen |  | PageBlue |
+----------+  |   with    |  +----------+
              |  Logging* |
              +-----------+
然而,Green 团队急于上线,所以他们已经把 Mixin 应用于LoggingMixin他们的 Page 类了。当我们把它应用到基Page类时,Mixin 就被应用了两次 😱
 突然间,Green 页面会把每个日志打印两次——这与我们最初的想法完全不同。
我们需要做的是确保每个 Mixin 只附加一次,即使我们尝试多次应用它。
一般来说,mixin 越通用,被多次使用的可能性就越高。作为 mixin 的作者,你无法控制它的使用方式,也无法预测它的使用情况。因此,为了安全起见,我们始终建议创建去重 mixin。
npm i @open-wc/dedupe-mixin
import { dedupeMixin } from '@open-wc/dedupe-mixin';
export const MyMixin = dedupeMixin(
  superclass =>
    class MyMixin extends superclass {
      // your mixin code goes here
    },
);
您可以在演示中看到确切的这种情况。
通过将 dedupeMixin 应用于 mixin 函数,在导出它之前,我们可以确保我们的 mixin 类只会生效一次,即使它被混合到继承链中的多个基类中。
- no-dedupe因记录两次 Green 而“失败”
- with-dedupe也成功记录了一次 Green
您可以在github上查看两者的源代码。
嵌套示例
你可能认为上面的例子太简单了,可以通过调整何时进行修改来解决。
 然而,在大多数实际场景中,情况要复杂得多 🙈 
 Mixins 可以扩展,仅仅因为你导入了一个类,并不意味着这个类已经预先应用了一些 Mixins。
考虑这个例子:
               +----------+               +----------+      +----------+
               |   Page   |               | Logging* |      | Feature  |
               |   with   |               +----+-----+      |   with   |
               | Logging* |                    |            | Metrics* |
               +-+--+--+--+               +----+-----+      +----+--+--+
                 |  |  |                  | Metrics* |           |  |
      +----------+  |  +-----------+      +----------+           |  +------
      |             |              |                             |
+-----+----+  +-----+-----+  +-----+----+                 +------+-------+
| PageRed  |  | PageGreen |  | PageBlue |                 | WaterFeature |
+----------+  +-----------+  |   with   |                 +--------------+
                             | Metrics* |
                             +----------+
- 页面通常只需要记录
- 然而,还有更先进的度量系统,可以扩展日志记录
- 指标是针对功能单独开发的
- 当我们现在想要在蓝色页面上获取相同的指标时,我们会得到重复的日志记录,而没有有意识地应用日志记录(我们会class PageBlue extends MetricsMixin(Page) {})
- 在这些情况下,只有重复数据删除才能发挥作用
何时不使用 Mixin
你现在可能会想“水坝真强大”,你说得对。我们现在应该用它来做所有事情吗?当然不行。
仅当您确实需要访问实例本身时才使用它。以下是一些不建议使用 Mixin 的坏例子。
 我想做if (this.isIE11()) { // ... }
对于任何“纯”函数,最好将其保留在类/原型之外。例如最好这样写
import { isIE11 } from './helpers.js';
if (isIE11()) {}
 我想要一个特别的this.addEventListener()
首先,覆盖内置函数可能非常危险。当你在类中需要实现一个非常具体的用例时,覆盖内置函数可能没问题。但是,如果在使用 Mixin 时突然发生这种情况,就会非常令人困惑。
最好将这些作为额外的函数来传递。这样人们就可以选择这些特殊功能,而不会“污染”他们的原型链。
import { specialAddEventListener } from './helpers.js';
specialAddEventListener(this, 'click', () => console.log('clicked'));
其次,所有属性/方法对于类/原型来说都是全局的。这意味着如果两个 mixin 使用相同的名称,就会发生冲突。因此,请确保为私有/受保护的方法使用特定的名称;如果使用通用名称,请确保在 mixin 名称/文档中能够清晰地识别。
何时使用 Mixin
如果您需要访问类实例本身。这意味着每个实例可能都有不同的设置。
一个有效的例子是,LocalizeMixin它允许你设置myEl.locale = 'de-DE';。在这种情况下,Mixin 需要提供并响应此属性。
import { dedupeMixin } from '@open-wc/dedupe-mixin';
export const LocalizeMixin = dedupeMixin(
  superclass =>
    class LocalizeMixin extends superclass {
      // this assumes a Mixin for LitElement
      static get properties() {
        return {
          locale: { type: String }
        };
      }
      updated(changedProperties) {
        super.updated(changedProperties);
        if (changedProperties.has('locale')) {
          // react to locale change
        }
      }
    },
);
我们学到了什么
使用 Mixins,您可以将共享逻辑引入多个类。它是一个非常强大的工具,但伴随强大而来的是责任。请务必负责任地使用它,并删除 Mixins 中的重复代码。
笔记
使用AsciiFlow制作的 Ascii 图形 照片
 由Vania Shows在Unsplash上拍摄
 后端开发教程 - Java、Spring Boot 实战 - msg200.com
            后端开发教程 - Java、Spring Boot 实战 - msg200.com