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

时间:2018-05-31 00:16:10

标签: javascript inheritance mixins composition

最近,我发现了两篇关于mixins的文章。这让我对哪一个比其他人更好感到困惑。

来自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!

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

enter image description here

  • 现在我很困惑,哪一个比其他人好。

  • 提供的优势比另一方更具优势。

  • 因此我应该选择使用哪一种。

2 个答案:

答案 0 :(得分:2)

一种简单的方法:

// mixin
let sayHiMixin = {
  sayHi() {
    alert(`Hello ${this.name}`);
  },
  sayBye() {
    alert(`Bye ${this.name}`);
  }
};

// usage:
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!

答案 1 :(得分: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 作为行为的容器,&#34;可以做&#34; 的事情,以及后来的类型< em>&#34;具有&#34; 某些行为,这种方法在技术上根本不承认mixins背后的概念,因为它本质上是基于一个孩子 - 父母或一个&#34;是&#34; 的关系。

第二种方法,即使用对象和Object.assign,乍一看看起来像是许多古老混合方法的现代变体,当时所有方法都使用了与对象相关联的行为和自编写的{{ {1}}方法......就像... extends

这种方法的独特之处在于它如何支持/解决从其他mixin创建的&#34; composite-mixins&#34; ... mixins。在我看来,通过extends(targetObject, mixinSourceObject)委派链接行为并将另一个基于对象的mixin分配给mixin的super属性是可行且优雅的。

我个人会花更多的时间在第二次提供的方法上进行研究。

还有另一种方法......基于功能的mixins。第二个基于对象的mixin-example重写成基于函数的挂件的代码看起来像这样......

&#13;
&#13;
__proto__
&#13;
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
&#13;
&#13;
&#13;

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

现在将证明两者的力量。仍在使用第二个示例代码和引入的基于函数的mixin方法,可以非常轻松地创建另一个用户将其隐藏起来.as-console-wrapper { max-height: 100%!important; top: 0; }来自公众,但确实通过它公开它name行为。当然,以下代码仅用于理解概念。在实践中很难实现一种&#34;混合复合混合物&#34; 就像那样......

&#13;
&#13;
say
&#13;
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
&#13;
&#13;
&#13;

基于功能的mixins的意见摘要

正如ES3提供的功能已经提供,通过闭包封装,明确授权功能以及通过.as-console-wrapper { max-height: 100%!important; top: 0; } / call应用不同的上下文,已经可以从基于mixin的组合开始。结合这些技术可以实现更强大的概念,如冲突解决,可以/将基于已经通过代理参考和一些功能组合演示的转发。也可以注入和传递附加状态。因此,人们甚至可以实现超越mixin的概念,例如TraitsStateful TraitsTalents,后者是真正符合JavaScript语言范例的组合概念。

是否使用mixin的经验法则

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