如何在Javascript中正确使用mixins

时间:2012-11-29 18:28:12

标签: javascript dry mixins

我正在组织一个小型企业应用程序,但我希望尽可能干。结果,我一直在关注mixin库。

我遇到了这个library并认为它可能是一个不错的选择,因为它允许您在运行时混合进出。另外,我可以只有一个基类(BaseView),只是混合它。

问题

  1. 有用的Mixins有哪些实际应用示例? (请不要再抽象例子)
  2. 我是否需要扩展课程,或者我可以使用此库来管理所有扩展和混音?

4 个答案:

答案 0 :(得分:4)

请参阅:

如果涉及基于JavaScript和基于角色的组合方法,如Mixins和Traits,我同时非常自以为是。我总是会指出一个图书馆不可知组合的2个基于功能的模式 - 首先是模块模式,其次是“Flight Mixin”模式,因为它已经被Angus Croll在2011年5月重新发现,命名和描述。但我也建议阅读我的论文从2014年4月开始。

  

问题

     
      
  • 1)有用Mixins的一些实际应用示例是什么? (请不要再抽象例子)
  •   
  • 2)我是否需要扩展课程,或者我可以使用此库来管理所有扩展和混合?
  •   

回答你的2个问题......

1st)[Observable]可能是 Mixins 最常见的真实示例之一。但这不是提供整个代码库的正确位置。来自Smart Talents chapter的相继增长的示例确实提供了[Queue]工厂的工作实现,这些工厂在开始时只使用不同的 Mixins ,例如[Enumerable][Allocable]但最后还应用了已经提到的[Observable]

2)只需使用您选择或需要的模块系统 - CommonJS or AMD。然后,您的工厂模块甚至实例/对象通过委派检索其他行为;因此,他们积极地{em> Mixin 或 Trait 模块call / apply

最后 - 缩短示例代码:

var Observable_SignalsAndSlots = (function () {

  var
    Event = function (target, type) {

      this.target = target;
      this.type = type;
    },

    EventListener = function (target, type, handler) {

      var defaultEvent = new Event(target, type);

      this.handleEvent = function (evt) {
        /* ... */
      };
      this.getType = function () {
        return type;
      };
      this.getHandler = function () {
        return handler;
      };
    },

    EventTargetMixin = function () {

      var eventMap = {};

      this.addEventListener = function (type, handler) {
        /* ... */
      };
      this.dispatchEvent = function (evt) {
        /* ... */
      };
    }
  ;

  return EventTargetMixin;

}).call(null);


var Queue = (function () {

  var
    global = this,

    Observable  = global.Observable_SignalsAndSlots,
  //Allocable   = global.Allocable,

    Queue,

    onEnqueue = function (queue, type) {
      queue.dispatchEvent({type: "enqueue", item: type});
    },
    onDequeue = function (queue, type) {
      queue.dispatchEvent({type: "dequeue", item: type});
    },
    onEmpty = function (queue) {
      queue.dispatchEvent("empty");
    }
  ;

  Queue = function () { // implementing the [Queue] Constructor.
    var
      queue = this,
      list = []
    ;
    queue.enqueue = function (type) {

      list.push(type);
      onEnqueue(queue, type);

      return type;
    };
    queue.dequeue = function () {

      var type = list.shift();
      onDequeue(queue, type);

      (list.length || onEmpty(queue));

      return type;
    };
    Observable.call(queue);
  //Allocable.call(queue, list);
  };

  return Queue;

}).call(null);


var q = new Queue;

q.addEventListener("enqueue", function (evt) {console.log("enqueue", evt);});
q.addEventListener("dequeue", function (evt) {console.log("dequeue", evt);});
q.addEventListener("empty", function (evt) {console.log("empty", evt);});

console.log("q.addEventListener : ", q.addEventListener);
console.log("q.dispatchEvent : ", q.dispatchEvent);

console.log("q.enqueue('the') ... ", q.enqueue('the'));     // "enqueue" Object {type: "enqueue", item: "the", target: Queue}
console.log("q.enqueue('quick') ... ", q.enqueue('quick')); // "enqueue" Object {type: "enqueue", item: "quick", target: Queue}
console.log("q.enqueue('brown') ... ", q.enqueue('brown')); // "enqueue" Object {type: "enqueue", item: "brown", target: Queue}
console.log("q.enqueue('fox') ... ", q.enqueue('fox'));     // "enqueue" Object {type: "enqueue", item: "fox", target: Queue}

console.log("q.dequeue() ... ", q.dequeue()); // "dequeue" Object {type: "dequeue", item: "the", target: Queue}
console.log("q.dequeue() ... ", q.dequeue()); // "dequeue" Object {type: "dequeue", item: "quick", target: Queue}
console.log("q.dequeue() ... ", q.dequeue()); // "dequeue" Object {type: "dequeue", item: "brown", target: Queue}
console.log("q.dequeue() ... ", q.dequeue()); // "dequeue" Object {type: "dequeue", item: "fox", target: Queue}
                                              // "empty"   Object {target: Queue, type: "empty"}
console.log("q.dequeue() ... ", q.dequeue()); // "dequeue" Object {type: "dequeue", item: undefined, target: Queue}
                                              // "empty"   Object {target: Queue, type: "empty"}
.as-console-wrapper { max-height: 100%!important; top: 0; }

答案 1 :(得分:3)

mixin 只是一个不同的概念,如何组织代码和继承。您当然可以将它与使用经典原型继承结合使用,但它也可以单独使用,可以这么说。

例如,我们不是创建“委托”对象属性/查找(如原型继承),而是真正“ form ”new来自多个其他对象的独立对象。有时这也被称为“多重继承”,单独使用Javascripts 原型继承无法轻松实现。

举个例子:

var pianist = {
   play: function() {}
};

var programmner: {
   code: function() {}
};

现在我们可以创建另一个 Object ,比如

var Jim = Object.create( null ); // create a fully self-defining object

extend( Jim, pianist );
extend( Jim, programmer );

这个伪extend方法看起来像(ES5):

function extend( target, source ) {
    Object.getOwnPropertyNames( source ).forEach(function( key ) {
        Object.defineProperty( target, key, Object.getOwnPropertyDescriptor(source, key)) });

    return target
}

我实际上没有正确回答你的问题,但我觉得你的问题没有真正的答案。它就像你要使用它一样真实,没有“特定于应用程序”的用例。

答案 2 :(得分:1)

我们使用名为Cocktail的mixin库(mixins ......得到它?)。它专门用于Backbone应用程序,但非常好。

我们written up details on our usage patterns比我在这里描述它做得更好。

答案 3 :(得分:0)

在面向对象的设计模式中,目标是尽可能消除继承,因为继承不灵活并且将无关实体混杂在一起。您应该对接口进行编程,而不是对实现进行编程。将不同之处与保持不变的地方分开。设计模式以策略模式的形式引入了委托的概念。但是由于JavaScript的动态特性,我们还可以轻松地使用mixins的概念来区分不同之处和相同之处。

选择mixins的另一个原因是,由于JavaScript是一种原型语言,它通过原型链将对象链接在一起,但是如果您过度使用此功能,则会发现链接原型在大型链中很慢。

实际上,通过使用混合,您可以将功能直接添加到对象的原型,而不是链接原型,从而减少了查找算法的开销。因此,例如,在下面,我们创建了两个单例对象“ speak”和“ fly”。这些是实现行为的对象。不同的事物可以说话和飞行,但是这些不同的事物不应相互继承。例如,您可能有一只野鸭和一只鹦鹉。

var speakable = {
    speak: function(){ return this.name + " speaks" }
}

var flyable = {
    fly: function(){ return this.name + " flies" }
}

在jQuery世界中以及ES6的纯净类语法之前,您将使用构造函数按如下所示添加mixins:

var MallardDuck = function(name){
  this.name = name;
}

var Parrot = function(name){
  this.name = name;
}

$.extend(MallardDuck.prototype, speakable, flyable);
$.extend(Parrot.prototype, speakable, flyable);

var duck = new MallardDuck('Bob');
var parrot = new Parrot('Jane');
console.log(duck.speak());
console.log(parrot.speak());

在没有jQuery且没有ES6的情况下,如果您想通过mixin工具扩展具有功能的对象,则只需编写自己的扩展mixin工具:

function extend(target){
  // if no mixin objects are provided, then return out of function
  if(!arguments[1])
    return;

  // the first argument is the target object we want to mix behavior into, so we start our loop at index 1
  for(var i = 1; j = arguments.length; i < j; i++) {
    // grab the singleton object to mixin into target
    var source = arguments[i];

    for(var propr in source) {
      // ensure we do not override a property that target already has
      if(!target[prop] && source.hasOwnProperty(prop)){
        target[prop] = source[prop];
      }
    }
  }
}

我们可以利用ES6中的Object.assign属性,而不是依赖像jQuery这样的外部库(强烈建议不要使用此库)或编写我们自己的实用程序mixin工具。此外,我们可以在ES6类中利用它。 Object.assign()方法用于将所有可枚举的自身属性的值从一个或多个源对象复制到目标对象。

const speakable = {
    speak: function(){ return `${this.name} speaks`; }
}

const flyable = {
    fly: function(){ return `${this.name} flies` }
}


class MallardDuck {
  constructor(name) {
    this.name = name;
    Object.assign(MallardDuck.prototype, speakable); // add the mixin to prototype chain. Alternatively, use 'this' to add directly to the new object being created
  }
}

class Parrot {
  constructor(name) {
    this.name = name;
    Object.assign(Parrot.prototype, speakable); // add the mixin to prototype chain. Alternatively, use 'this' to add directly to the new object being created
  }
}

const duck = new MallardDuck('Mallard Duck');
const parrot = new Parrot('Parrot');
console.log(duck.speak());
console.log(parrot.speak());