使用模块模式时的 Javascript 混合

Posted

技术标签:

【中文标题】使用模块模式时的 Javascript 混合【英文标题】:Javascript mixins when using the module pattern 【发布时间】:2013-07-11 23:25:45 【问题描述】:

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

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

    //"constructor"
    function module() 

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

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

    return module;

这是一个混合对象:

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

理想情况下,我想将来自其他对象的一些方法作为私有方法和一些作为公共方法混合,这样我就可以调用一些“扩展”函数,参数为“私有”/“公共”。所以,那个

mixin(myModule, myMixin, "private");

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

mixin(myModule, myMixin, "public");

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

我尝试过使用将属性从一个原型复制到另一个原型的方法,我尝试过下划线扩展方法将对象的属性从一个原型复制到另一个,以及介于两者之间的各种东西。我想我在这一点上对范围和原型有点转变,并且希望在使用模块模式时如何最好地做这样的混合。请注意,对象 myMixin 的外观并不重要(无论是向原型添加函数还是模块本身),我只是想找出一些使其工作的方法。

谢谢!

【问题讨论】:

【参考方案1】:

这样 [一些代码] 只需调用 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()…;
            …
        
    ;
)();

有了这样的接口,就可以很容易地构造一个使用 this 作为 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 模式也可以应用于普通模块或在其声明中,上面的示例只显示了带有原型的类。

关于一些很好的例子,以及(无状态)特征、(有状态)Mixin 和它们的“特权”对应物之间的理论区别,请查看this presentation。

【讨论】:

【参考方案2】:

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

【讨论】:

【参考方案3】:

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

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

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

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

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

因此,已经处于语言核心级别的 JavaScript 确实提供了基于函数的 比任何可用的 extend(s)mixin 都强大的 Mixin 模式 它的实现免费提供委托并且能够通过 state 周围几乎所有被指责的助手都缺乏,除非有 以一种相当迂回的方式再次实现此功能的努力 (或直言不讳)。

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.

这种特殊的模式也是构成 pure 依赖于冲突解决功能的基于函数的特征 由不会公​​开此功能的“代理”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; 

【讨论】:

以上是关于使用模块模式时的 Javascript 混合的主要内容,如果未能解决你的问题,请参考以下文章

混合模式程序集是针对运行时的“v2.0.50727”版本构建的

混合模式程序集是针对版本X构建的,如果没有其他配置信息,则无法在运行时的版本Y中加载

混合模式程序集是针对运行时的“v2.0.50727”版本构建的,无法在 4.0 运行时中加载 - 研究的解决方案不起作用

JavaScript继承基础讲解,原型链借用构造函数混合模式原型式继承寄生式继承寄生组合式继承

移动端获取用户在屏幕滑动方向(javascript)混合模式封装

Flutter混合开发模式下的代码调试