在 js 中使用 mixin 的最佳方法是什么?

Ank*_*aha 5 javascript inheritance composition mixins

最近,我看到了两篇关于 mixin 的文章。这让我在哪个比另一个更好之间感到困惑。

第一个来自mdn

var calculatorMixin = Base => class extends Base {
  calc() { }
};
var randomizerMixin = Base => class extends Base {
  randomize() { }
};

class Foo { }
class Bar extends calculatorMixin(randomizerMixin(Foo)) { }
Run Code Online (Sandbox Code Playgroud)

来自https://javascript.info/mixins 的第二个

let sayMixin = {
  say(phrase) {
    alert(phrase);
  }
};

let sayHiMixin = {
  __proto__: sayMixin, // (or we could use Object.create to set the prototype here)

  sayHi() {
    // call parent method
    super.say(`Hello ${this.name}`);
  },
  sayBye() {
    super.say(`Bye ${this.name}`);
  }
};

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

// copy the methods
Object.assign(User.prototype, sayHiMixin);

// now User can say hi
new User("Dude").sayHi(); // Hello Dude!
Run Code Online (Sandbox Code Playgroud)

在这些场景中创建的对象也具有不同的组成/结构。

在此处输入图片说明

  • 现在我很困惑,哪个更好。

  • 一个比另一个提供什么优势。

  • 因此,我应该更喜欢使用哪一个。

Pet*_*ger -1

我完全同意贾里德·史密斯的观点。就像任何工具或工具集一样,人们需要知道是否要使用它。如果出于任何原因选择mixin的概念,那么我们应该真正了解它的能力和缺失的能力,特别是应用于 JavaScript 的编程范式/概念时。

而我的观点是固执己见,因此以下将从我自己的角度提出一些想法和技术方法。其他解决方案更为广泛,我有时也会使用其中一些。

让我们看看OP提供的第一个来源。例如,如果将上面给出的示例重写为类似......

const calculatorMixin = Base => class extends Base {
  calc() { }
};
const randomizerMixin = Base => class extends Base {
  randomize() { }
};

class Baz { }
const calcAndRandomizeMixin = calculatorMixin(randomizerMixin(Baz));

class Biz extends calcAndRandomizeMixin { }

const biz = new Biz;

console.log('(biz instanceof Biz) ? ', (biz instanceof Biz));
console.log('(biz instanceof Baz) ? ', (biz instanceof Baz));
console.log('(biz instanceof calcAndRandomizeMixin) ? ', (biz instanceof calcAndRandomizeMixin));
Run Code Online (Sandbox Code Playgroud)
.as-console-wrapper { max-height: 100%!important; top: 0; }
Run Code Online (Sandbox Code Playgroud)

...那么我最关心的是该方法本身,因为它完全基于类及其扩展。“ mixin 是由类工厂立即创建的类。他们总是延长另一个班级。因此它是纯粹的继承。

即使有人编写这样一个mixin 类作为“可以做”事情的行为容器,并且稍后的类型“具有”某种行为,但这种方法在技术上根本不承认 mixin 背后的概念,因为它是它本质上是基于孩子-父母或“是”关系。

第二种方法,利用对象 和Object.assign,乍一看像是许多古老 mixin 方法的现代变体,当时所有方法都使用与对象相关的行为和自编写extends方法的组合......比如extends(targetObject, mixinSourceObject)......。

这种方法的独特之处在于它如何支持/解决“复合混合” ...从其他混合创建的混合。在我看来,通过委托链接行为super并将另一个基于对象的 mixin 分配给 mixin 的属性是可行且优雅的。__proto__

我个人会花更多的时间来研究/研究第二种提供的方法。

还有另一种方法......基于函数的混合。第二个基于对象的混合示例的代码重写为基于函数的挂件,看起来像这样......

const sayMixin = (function () {   // basic function-based mixin.

  // shared code.                 //
  function say(phrase) {          // a single implementation
    console.log(phrase);          // of `say` behavior ...
  }                               //

  // return function based mixin. //
  return function sayMixin () {   // ... that might get applied
    this.say = say;               // many times but always as
  };                              // reference / shared code.

}());

const sayHiMixin = (function () { // function-based *composite-mixin*.

  // shared code.

  // object that helps with behavior forwarding.
  const sayProxy = {};

  // apply behavior of `sayMixin`.
  sayMixin.call(sayProxy);

  // a single implementation of `sayHi` behavior.
  function sayHi() {
    sayProxy.say(`Hello ${this.name}!`);  // forwarding.
  }
  // a single implementation of `sayBye` behavior.
  function sayBye() {
    sayProxy.say(`Bye ${this.name}!`);    // forwarding.
  }

  // return function based composite mixin.
  return function sayHiMixin () {
    this.sayHi = sayHi;   // - always shares one and the ...
    this.sayBye = sayBye; //   ... same implementation(s).
  };

}());


class User {
  constructor(name) {

    // public property.
    this.name = name;
  }
}
// apply the composite `sayHiMixin`.
sayHiMixin.call(User.prototype);

// now a `User` can say hi and bye
const dude = new User('Dude');

dude.sayHi();   // Hello Dude!
dude.sayBye();  // Bye Dude!

console.log('dude.name : ', dude.name); // Dude
Run Code Online (Sandbox Code Playgroud)
.as-console-wrapper { max-height: 100%!important; top: 0; }
Run Code Online (Sandbox Code Playgroud)

即使在今天,也有一些很好的理由支持选择这种方法。首先,它与OP提到的其他两个都有共同点......不需要额外的库。其次,与其他环境不同,它确实可以在每个给定的 ES3 环境中运行。第三,基于函数的方法将 mixin 实现为“适用类型”,因此可以免费获得委托和封装。

两者的威力现在就要展现出来了。仍然使用第二个示例代码和引入的基于函数的 mixin 方法,可以很容易地创建另一个用户,该用户name向公众隐藏其初始值,但确实通过其say行为公开它。当然,下面的代码只是为了理解概念。在实践中人们几乎不会意识到像这样的“混合复合混合” ......

const sayMixin = (function () {   // basic function-based mixin.

  // shared code.                 //
  function say(phrase) {          // a single implementation
    console.log(phrase);          // of `say` behavior ...
  }                               //

  // return function based mixin. //
  return function sayMixin () {   // ... that might get applied
    this.say = say;               // many times but always as
  };                              // reference / shared code.

}());

const sayHiMixin = (function () { // function-based *composite-mixin*.

  // shared code.

  // object that helps with behavior forwarding.
  const sayProxy = {};

  // apply behavior of `sayMixin`.
  sayMixin.call(sayProxy);

  // a single implementation of `sayHi` behavior.
  function sayHi() {
    sayProxy.say(`Hello ${this.name}!`);  // forwarding.
  }
  // a single implementation of `sayBye` behavior.
  function sayBye() {
    sayProxy.say(`Bye ${this.name}!`);    // forwarding.
  }

  // // return function based composite mixin.
  // return function sayHiMixin () {
  //   this.sayHi = sayHi;   // - always shares one and the ...
  //   this.sayBye = sayBye; //   ... same implementation(s).
  // };

  // return function based hybrid composite mixin.
  return function sayHiMixin (properties) {
    if (properties && (typeof properties === 'object')) {

      console.log('sayHiMixin :: payload bound to behavior');

      this.sayHi = sayHi.bind(properties);    // - creates each a ...
      this.sayBye = sayBye.bind(properties);  //   ... new reference.
    } else {
      console.log('sayHiMixin :: direct behavior reference');

      this.sayHi = sayHi;   // - always shares one and the ...
      this.sayBye = sayBye; //   ... same implementation(s).
    }
  };

}());


class User {
  constructor(name) {

    // public property.
    this.name = name;
  }
}
// apply the composite `sayHiMixin`.
sayHiMixin.call(User.prototype);

// now a `User` can say hi and bye
const dude = new User('Dude');

dude.sayHi();   // Hello Dude!
dude.sayBye();  // Bye Dude!

console.log('dude.name : ', dude.name); // Dude


class AnotherUser {
  constructor(name) {

    // local property + public accessor methods.
    sayHiMixin.call(this, { name: name });
  }
}

// now a `User` can say hi and bye
const john = new AnotherUser('John');

john.sayHi();   // Hello John!
john.sayBye();  // Bye John!

console.log('john.name : ', john.name); // undefined
Run Code Online (Sandbox Code Playgroud)
.as-console-wrapper { max-height: 100%!important; top: 0; }
Run Code Online (Sandbox Code Playgroud)

基于函数的 mixins 的观点总结

凭借 ES3 以来已经提供的功能、通过闭包进行封装、显式功能委托以及通过call/应用不同的上下文apply,人们已经可以从基于 mixin 的组合开始了。结合这些技术可以实现更强大的概念,例如冲突解决,这些概念可以/将基于已经演示的通过代理引用和某些函数组合的转发。注入和传递附加状态也是可能的。因此,我们甚至可以实现超越 mixin 的概念,例如TraitsStateful TraitsTalents,而后者是真正适合 JavaScript 语言范式的组合概念。

是否使用 mixin 的经验法则

仅当代码重用可以用可观察之类的形容词来描述和/或在整个系统内不相似的类和/或异构类型需要相同的附加行为时才采用它。