使用模块模式时Javascript mixins

时间:2013-07-13 15:21:03

标签: javascript design-patterns mixins module-pattern

我一直在使用模块模式一段时间,但最近已经开始想要将函数和属性混合到它们中以增加代码重用。我已经阅读了一些关于这个主题的好资源,但对于最好的方法仍然有点不确定。这是一个模块:

var myModule = function () {
    var privateConfigVar = "Private!";

    //"constructor"
    function module() {}

    module.publicMethod = function () {
        console.log('public');
    }

    function privateMethod1() {
        console.log('private');
    }

    return module;
}

这是一个mixin对象:

var myMixin = function () {};
Mixin.prototype = {
    mixinMethod1: function () {
        console.log('mixin private 1');
    },
    mixinMethod2: function () {
        console.log('mixin private 2');
    }
};

理想情况下,我想将其他对象中的某些方法混合为私有方法,将某些方法混合为公共方法,这样我就可以调用一些“扩展”函数,将param称为“私有”/“公共”。那么,

mixin(myModule, myMixin, "private");

只需调用mixinMethod1()并具有正确的范围,即可在myModule中使myMixin方法可用,并且:

mixin(myModule, myMixin, "public");

通过调用module.mixinMethod1()使myModuin方法在myModule中可用并具有正确的范围

我尝试使用将属性从一个原型复制到另一个原型的方法,我尝试使用下划线扩展方法将对象的属性从一个复制到另一个,以及两者之间的各种事物。在这一点上,我认为我对范围和原型有点转变,并且在使用模块模式时如何最好地进行这样的混合操作会有一些方向。请注意,myMixin的对象是什么样的(无论是向原型添加函数还是模块本身)并不重要,我只想弄清楚一些方法使其工作。

感谢!

3 个答案:

答案 0 :(得分:2)

  

这样[某些代码]只需调用mixinMethod1()并具有正确的范围,就可以在myModule中使myMixin方法可用

那是不可能的。您无法通过调用函数来修改范围,尤其是不能从外部修改范围。另请参阅Is it possible to import variables in JavaScript?了解其设计原因。

那么,可以你做什么?

从模块外部

module个函数的私有范围内没有任何内容。显然,你不能使用模块的私有功能。您可以使用方法扩展其原型(这是最常见的),您甚至可以decorate its constructor function。在这些中,您可以使用自己的私有函数,完全静态函数或类特定函数。

var myMixin = (function() {
    // everything class-unspecific but mixin-local
    var staticMixinVariables, …;
    function globalPrivateFunction(){…}
    function staticMethod(){…}

    return function(mod) {
        // everything class-specific
        // also using the locals from above
        mod.staticHelper = function() { staticMixinVariable … };
        mod.prototype.mixinMethod1 = staticMethod;
        mod.prototype.mixinMethod2 = function(){…};
        …
    };
})();

// Example:
myMixin(SomeClass)

来自模块

在模块代码中使用mixin可以提供更大的灵活性。

var myMixin = (function() {
    // everything class-unspecific but mixin-local
    …
    return {
        publicHelper1: function(){…},
        publicHelper2: function(){…},
        decorateInstance: function(o) {
            o.xy = …;
        },
        extendPrototype: function(proto) {
            // everything class-specific
            // also using the locals from above
            proto.mixinMethod1 = staticMethod;
            proto.mixinMethod2 = function(){…};
            …
        }
    };
})();

使用这样的接口,构建一个使用它作为mixin(不是从它继承)的类变得容易:

var myClass = (function() {
    function Constructor() {
        myMixin.decorateInstance(this);
        …
    }
    Constructor.prototype.method1 = function() { myMixin.publicHelper1() … };
    Constructor.prototype.method2 = function() { … };
    myMixin.extendPrototype(Constructor.prototype);
    Constructor.myHelper = myMixin.publicHelper2; // re-export explicitly
    return Constructor;
})();

然而,mixin永远不会访问私有类变量,也不能呈现私有的,特定于类的API。尽管如此,我们仍然可以使用依赖注入来明确地提供该访问(并使mixin工厂生效):

var myClass = (function() {
    var … // private class functions and variables
    var mixer = myMixin(privateClassHelper,
                        privateClassVariable,
                        function setPrivateVar(x) {…},
                        … );
    var myHelper = mixer.customHelper, … // local "aliases"
    function Constructor(localX) {
        mixer.decorateInstance(this, localX);
        …
    }
    … // further using the class-specific private mixer
    return Constructor;
})();

并非所有上面显示的技术都需要在每个mixin中使用,只需选择您需要的技术。并非所有可能的技术都显​​示在上面的例子中,同样:-) mixin模式也可以应用到普通模块或其声明中,上面的例子只显示了带有原型的类。

对于一些好的例子,以及(无国籍)特征,(有状态的)Mixins和他们的“特权”对应物之间的理论区别,请看this presentation

答案 1 :(得分:0)

with关键字对于定义范围非常有用,但它也有一些缺点(在严格模式下禁止使用)。

使用with关键字,您可以在模块正文中定义私有变量privateScope,其中包含所有的provate方法:

var myModule = function () {

    var privateConfigVar = "Private!";
    var privateScope = {};

    //"constructor"
    function module() {}

    var proto = module.prototype;//avoids multiple attribute lookup

    //Let's re-define you example' private method, but with a new strategy
    privateScope['privateMethod1'] = function() {
        console.log('private');
    }

    proto.publicMethod = function () {
        with(privateScope){
            //this call should work
            privateMethod1();
        }
        console.log('public');
    }

    proto.publicMethod2=function(name,fn){
        with(privateScope){
            //this will be defined later by a Mixin
            otherPrivateMethod();
        }
        console.log('public2');
    }

    proto.definePrivateFunction=function(name,fn){
        privateScope[name] = fn;
    }



    return module;
}

您的mixin将使用我们刚刚定义的definePrivateFunction将私有方法添加到私有范围:

//An example mixin implementation
function Mixin(source,target,flag){
    if(flag==="private"){
        for(var currentMethodName in source){
            target.definePrivateFunction(currentMethodName,source[currentMethod])
        }
    }else{
        for(var currentMethodName in source){
            target[currentMethodName]=source[currentMethod];
        }
    }
}

以下代码应该可以正常工作:

var test = myModule();
var testInstance = new test();
testInstance.publicMethod();// will call the private method defined internally

Mixin({
          otherPrivateMethod:function(){
                        console.log("other Prvate Method called")
                      }
      },test.prototype,"private");

testInstance.publicMethod2();// will call the private method defined by the mixin

答案 2 :(得分:0)

  

理想情况下,我想将其他对象中的某些方法混合为私有方法,将某些方法混合为公共方法,这样我就可以调用一些“扩展”函数,将param称为“私有”/“公共”。 ...

正如已经提到的那样,没有办法实现这个目标。

  

所以,那......通过调用mixinMethod1()并使用正确的范围,使myModuin方法在myModule中可用,并且:...通过调用module.mixinMethod1()使myModuin方法在myModule中可用并具有正确的范围

参考范围 ...这是一个由函数创建的封闭地址空间。 除闭包之外, scope 仅在函数运行时期间可用 在这个功能的身体内。它永远不会被操纵/欺骗。

一个人正在寻找的是 context 。 JavaScript,在很多方面都很高 动态,建立在后期绑定(调用方法的对象/目标/上下文) on得到评估/在运行时查找)和two kinds of delegation。 通过“走原型链”自动委派上下文 或者通过每个函数对象提供的两种调用方法之一显式地显式 - callapply

因此,已经在语言核心级别的JavaScript确实提供了基于功能的功能 Mixin模式比任何可用的extend(s)mixin更强大 它的实现提供了免费的委托,并且能够通过 在周围几乎每个被指责的帮助者都缺乏,除非那里 是以相当迂回的方式再次实现此功能的努力 (或者直截了当地说道。)

Bergi他的explanation已经获得了赏金。 在他的答案的最后一段中,有一个链接到我的资源 在推荐的讲话后3个月已经过时了。由于没有 足够的声誉点,我无法直接评论他的答案。为了这 我现在抓住机会指出我个人研究的最新状态 理解»The many talents of JavaScript for generalizing Role Oriented Programming approaches like Traits and Mixins«

再次回答OP的问题。

我将从假定的模块模式更改两个第一个给定的代码示例 并且相当示例性地提供了针对普通构造函数的mixin代码库 而我同时试图按顺序称为“代理”和/或“双向文本”混合 归结为一次委托两个不同的目标/上下文对象的机制。 从而展示了一种基于纯函数的mixin模式,它可能最接近于什么 OP试图实现。

var MyBicontextualMixin = function (localProxy) {

  localProxy.proxifiedAccessible = function () {
    console.log("proxified accessible.");
  };
  this.publiclyAccessible = function () {
    console.log("publicly accessible.");
  };
};

var MyConstructor = function () {
  var localProxy = {};
  MyBicontextualMixin.call(this, localProxy);

  var locallyAccessible = localProxy.proxifiedAccessible;

  // call 'em
  locallyAccessible();        // "proxified accessible."
  this.publiclyAccessible();  // "publicly accessible."
};

(new MyConstructor);

// will log:
//
// proxified accessible.
// publicly accessible.

这种特殊模式也是构成纯粹的基础 基于函数的 Traits 依赖于冲突解决功能 由“proxined”Mixins提供,不会公开此功能 公开。

如果没有结束那个理论,就会有一个“现实世界的例子”, 完全用各种可重用的mixin组成Queue模块 崇拜 DRY 的方法。它也应该回答OP的问题 如何仅在模块上实现封装阐述构建 基于模式和功能的mixin组合。

var Enumerable_first_last_item = (function (global) {

  var
    parseFloat = global.parseFloat,
    math_floor = global.Math.floor,

  // shared code.

    first = function () {
      return this[0];
    },
    last = function () {
      return this[this.length - 1];
    },
    item = function (idx) {
      return this[math_floor(parseFloat(idx, 10))];
    }
  ;

  return function () { // [Enumerable_first_last_item] Mixin.
    var enumerable = this;

    enumerable.first = first;
    enumerable.last = last;
    enumerable.item = item;
  };

}(window || this));



var Enumerable_first_last_item_proxified = function (list) {
  Enumerable_first_last_item.call(list);

// implementing the proxified / bicontextual [Enumerable_first_last_item] Mixin.
  var enumerable = this;

  enumerable.first = function () {
    return list.first();
  };
  enumerable.last = function () {
    return list.last();
  };
  enumerable.item = function (idx) {
    return list.item(idx);
  };
};



var Allocable = (function (Array) {

  var
    array_from  = ((typeof Array.from == "function") && Array.from) || (function (array_prototype_slice) {
      return function (listType) {

        return array_prototype_slice.call(listType);
      };
    }(Array.prototype.slice))
  ;

  return function (list) { // proxified / bicontextual [Allocable] Mixin.
    var
      allocable = this
    ;
    allocable.valueOf = allocable.toArray = function () {

      return array_from(list);
    };
    allocable.toString = function () {

      return ("" + list);
    };
    allocable.size = function () {

      return list.length;
    };
    Enumerable_first_last_item_proxified.call(allocable, list);
  };

}(Array));



var Queue = (function () {          // [Queue] Module.

  var
    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({type: "empty"});
    }*/,
    onEmpty = function (queue) {
    //queue.dispatchEvent("empty");
    },

    Queue = function () {           // [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);       // applying the [Observable] Mixin.
      Allocable.call(queue, list);  // applying the bicontextual [Allocable] Mixin.
    },

    isQueue = function (type) {
      return !!(type && (type instanceof Queue));
    },
    createQueue = function () {     // [Queue] Factory.
      return (new Queue);
    }
  ;

  return {                          // [Queue] Module.
    isQueue : isQueue,
    create  : createQueue
  };

}());



var q = Queue.create();

//q.addEventListener("enqueue", function (evt) {/* ... */});
//q.addEventListener("dequeue", function (evt) {/* ... */});
//q.addEventListener("empty", function (evt) {/* ... */});


console.log("q : ", q);                     // { .., .., .., }
console.log("q.size() : ", q.size());       // 0
console.log("q.valueOf() : ", q.valueOf()); // []

"the quick brown fox jumped over the lazy dog".split(/\s+/).forEach(function (elm/*, idx, arr*/) {
  console.log("q.enqueue(\"" + elm + "\")", q.enqueue(elm));
});

console.log("q.size() : ", q.size());       // 9
console.log("q.toArray() : ", q.toArray()); // [ .., .., .., ]

console.log("q.first() : ", q.first());     // "the"
console.log("q.last() : ", q.last());       // "dog"
console.log("q.item(2) : ", q.item(2));     // "brown"
console.log("q.item(5) : ", q.item(5));     // "over"

console.log("q.dequeue()", q.dequeue());    // "the"
console.log("q.dequeue()", q.dequeue());    // "quick"
console.log("q.dequeue()", q.dequeue());    // "brown"
console.log("q.dequeue()", q.dequeue());    // "fox"
console.log("q.dequeue()", q.dequeue());    // "jumped"

console.log("q.size() : ", q.size());       // 4
console.log("q.toArray() : ", q.toArray()); // [ .., .., .., ]

console.log("q.first() : ", q.first());     // "over"
console.log("q.last() : ", q.last());       // "dog"
console.log("q.item(2) : ", q.item(2));     // "lazy"
console.log("q.item(5) : ", q.item(5));     // undefined
.as-console-wrapper { max-height: 100%!important; top: 0; }