【问题标题】:What is the best way to use mixins in js?在 js 中使用 mixins 的最佳方法是什么?
【发布时间】:2018-11-09 22:00:11
【问题描述】:

最近,我看到了两篇关于 mixin 的文章。这让我很困惑,哪个更好。

来自mdn的第一个

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

class Foo { }
class Bar extends calculatorMixin(randomizerMixin(Foo)) { }

第二个来自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!

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

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

  • 一个比另一个有什么优势。

  • 因此我应该使用哪一个。

【问题讨论】:

  • ...或者是否首先使用mixins...但是说真的,没有标准的方法。随心所欲地去做。 Mixin 也有自己的问题。
  • @JaredSmith,这个视频有点说服我使用 mixins -- youtube.com/…
  • 什么是mixin?扩展对象功能的东西。做类似Object.assign(Cls.prototype, someMixinObj) 的事情似乎非常合理和简单。无需过于复杂。
  • 用它们做什么? Mixins 是解决问题的方法。你有什么问题是要解决的? Mixin 有以下问题:名称冲突,没有内置的自省(例如instanceof),没有标准的 JavaScript 方法,难以找到方法的实现位置。你可以通过纪律和严格的约定来解决很多这些问题,但是如果你要处理由 mixins 引起的问题,你必须确保好处更大。
  • FWIW,当我使用 mixins I use this 时。它与其他所有 mixin 库一样有权衡,但我可以接受它。

标签: javascript inheritance mixins composition


【解决方案1】:

我完全同意贾里德·史密斯的观点。就像使用任何工具或工具集一样,需要知道是否要使用它。如果出于任何原因选择了 mixins 的概念,那么你应该真正知道它的能力和遗漏了什么,尤其是在应用于 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));
.as-console-wrapper { max-height: 100%!important; top: 0; }

...那么我最大的担忧来自于方法本身,因为它完全基于类及其扩展。 “mixins” 是类,由类工厂即时创建。他们总是扩展另一个类。因此它是纯粹的继承。

尽管有人正在编写这样的 mixin-class 作为行为容器,“可以” 做一些事情,而后来的类型 “有" 某些行为,从技术上讲,这种方法根本不承认 mixins 背后的概念,因为它的本质是基于子-父或 “是” 关系。

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

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

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

还有另一种方法……基于函数的混入。第二个基于对象的 mixin-example 的代码重写为它的基于函数的挂件确实看起来像这样......

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
.as-console-wrapper { max-height: 100%!important; top: 0; }

即使在今天,选择这种方法也有一些很好的论据。首先,它与 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
.as-console-wrapper { max-height: 100%!important; top: 0; }

关于基于函数的 mixins 的观点总结

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

是否使用 mixin 的经验法则

只有在代码重用可以用像 observable 这样的形容词来描述和/或在整个系统中,不相似的类和/或异构类型需要相同的附加行为。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-04
    • 2013-11-26
    • 1970-01-01
    相关资源
    最近更新 更多