【问题标题】:How to use mixins properly in Javascript如何在 Javascript 中正确使用 mixins
【发布时间】:2012-11-17 21:52:08
【问题描述】:

我正在组织一个小型企业应用程序,但希望尽可能 DRY。因此,我一直在研究 mixin 库。

我遇到了这个library 并认为它可能是一个不错的选择,因为它允许您在运行时混入和混出。此外,例如,我可以只拥有一个基类 (BaseView),然后将其混入。

问题

  1. 有哪些有用的 Mixin 的实际应用示例? (请不要再有抽象的例子)
  2. 我什至需要扩展类还是只使用这个库来管理所有扩展和 mixin?

【问题讨论】:

    标签: javascript dry mixins


    【解决方案1】:

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

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

    举个例子:

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

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

    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】:

      另请参阅:

      如果涉及到 JavaScript 和基于角色的组合方法,例如 Mixins 和 Traits,我同时非常固执己见。我总是会指出 2 个纯基于函数的模式的库不可知组合 - 首先是模块模式,其次是“Flight Mixin”模式,因为它已被 Angus Croll 在 2011 年 5 月重新发现、命名和描述。但我也建议阅读我 2014 年 4 月的一篇论文。

      问题

      • 1) 有哪些有用的 Mixin 的实际应用示例? (请不要再抽象的例子)
      • 2) 我什至需要扩展类,还是可以只使用这个库来管理所有扩展和 mixin?

      回答您的 2 个问题 ...

      1st) [Observable] 可能是 Mixins 在现实世界中最常见的例子之一。但这不是提供其整个代码库的正确位置。来自Smart Talents chapter 的不断增长的示例确实提供了[Queue] 工厂的工作实现,该工厂一开始只是使用不同的Mixins,例如[Enumerable][Allocable],但最后也应用了已经提到的@ 987654331@.

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

      最后 - 缩短的示例代码:

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

      【讨论】:

        【解决方案3】:

        我们使用一个名为 Cocktail 的 mixin 库(mixins...明白了吗?)。它专门用于 Backbone 应用程序,但非常好。

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

        【讨论】:

          【解决方案4】:

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

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

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

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

          在 jQuery 世界中以及在 ES6 的干净类语法之前,您可以使用构造函数按如下方式添加 mixin:

          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());
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-07-05
            • 2012-07-05
            • 1970-01-01
            • 2022-12-31
            • 2018-05-08
            相关资源
            最近更新 更多