在js中使用mixins的最佳方法是什么? [英] What is the best way to use mixins in js?

查看:76
本文介绍了在js中使用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)) { } 

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! 

在这些情况下创建的对象也具有不同的组成/结构.

  • 现在我很困惑,哪一个比另一个更好.

  • 一个提供了比其他提供的优势.

  • 因此,我更喜欢使用哪一个.

解决方案

我完全同意Jared Smith的观点.像使用任何工具或工具集一样,需要知道是否要使用它.如果出于某种原因选择了 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背后的概念,因为从本质上讲,它是基于子父母或是" 关系. /p>

第二种方法,利用对象和Object.assign,乍一看似乎是许多古老的mixin方法的现代变体,这些方法后来都都使用了链接到对象的行为和自己编写的extends方法...就像... extends(targetObject, mixinSourceObject).

这种方法的独特之处在于它如何支持/解决由其他mixin创建的"composite-mixins" ... mixin.在我看来,通过super委派来链接行为并将另一个基于对象的mixin分配给mixin的__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实现为适用类型",因此可以免费获得委托和封装.

两者的力量现在将得到展示.仍然可以很容易地使用2nd的示例代码和引入的基于函数的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应用不同的上下文,就已经可以从基于混合的合成开始.结合使用这些技术可以实现更强大的概念,例如冲突解决,它可以/将已经基于通过代理引用和某些功能组合的演示转发.注入和绕过附加状态也是可能的.因此,甚至可以实现超越混合的概念,例如 特质 有状态特征 mdn

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

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

Second one from 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!

Object created in these scenarios has different composition/structure too.

  • Now I am confused like which one is better that other.

  • What advantages does one provide than the other.

  • And hence which one should I prefer to use.

解决方案

I fully agree with Jared Smith. Like with any tool or toolset one needs to be aware of whether to use it at all. If for any reason one chooses the concept of mixins one should really know what it is capable of and what it does miss, especially if applied to the programming paradigms/concepts of JavaScript.

And there I'm opinionated, thus the following thoughts and technical approaches will be provided from my very own point of view. Other solutions are much more widespread and some of them I sometimes use too.

Let's take the first source provided by the OP. If one, for instance, rewrites the above given example to something like ...

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; }

... then my biggest concern comes with the approach itself for it is entirely based on classes and their's extension. The "mixins" are classes, created instantly by class-factories. They always extend another class. Thus it is pure inheritance.

And even though one was writing such a mixin-class as a container of behavior that "can do" things, and a type later "has a" certain behavior, this approach technically does not at all acknowledge the concept behind mixins, because by it's very nature it is based on a child-parent or an "is a" relationship.

The second approach, with making use of objects and Object.assign, at first glance looks like a modern variant of the many ancient mixin approaches that back then all used a combination of behavior linked to objects and a self written extends method ... like ... extends(targetObject, mixinSourceObject).

Unique to this approach is how it supports/solves "composite-mixins" ... mixins that are created from other mixins. Linking behavior by super delegation and assigning another object-based mixin to a mixin's __proto__ property is, in my opinion, viable and elegant.

I personally would spent more time playing with / doing research on this secondly provided approach.

And there is yet another approach ... function-based mixins. The code of the second, object-based mixin-example rewritten into it's function-based pendant does look like this ...

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; }

There are some good arguments for even nowadays choosing this approach. Firstly, and this it has in common with both of the other ones mentioned by the OP ... no need for an additional library. Secondly, it does run in every given ES3 environment, unlike the others. At third, a function based approach implements mixins as "applicable types", thus one gets delegation and encapsulation for free.

The power of both is going to be demonstrated now. Still working with the 2nd's example code and the introduced function based mixin approach one very easily could create another user that hides it's initial name from public but does expose it via it's say behaviors. Of course the following code is just for understanding the concepts. One hardly would realize in practice kind of a "hybrid composite mixin" like that ...

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; }

Opinionated summary on function based mixins

Just with what functions already since ES3 do offer, encapsulation via closures, explicit delegation of functionality and applying different contexts via call/apply, one already can start with mixin based composition. Combining this techniques enables more powerful concepts like conflict resolution that could/would be based on the already demonstrated forwarding via a proxy reference and some function composition. Injection of and passing around additional state is possible too. Thus one could even implement concepts that reach beyond mixins like Traits, Stateful Traits, and Talents with the latter being the composition concept that really fits the language paradigms of JavaScript.

Rule of thumb of whether to use a mixin

Go for it only in case the code-reuse can be described by an adjective like observable and/or if, within the entire system, unsimilar classes and/or heterogeneous types are in need of the same additional behavior.

这篇关于在js中使用mixins的最佳方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆