2

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

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 的外观并不重要(无论是向原型添加功能还是模块本身),我只是想找出一些使其工作的方法。

感谢!

4

3 回答 3

2

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

这不可能。您不能通过调用函数来修改作用域,尤其是不能从外部调用。另请参阅JavaScript 中是否可以使用变量?import出于设计原因。

那么,你能做什么呢?

从模块外部

module对函数的私有范围没有任何影响。显然,您不能使用模块的私有功能。你可以用方法扩展它的原型(这是最常见的),你甚至可以装饰它的构造函数。在这些函数中,您可以使用自己的私有函数,可以是完全静态的函数,也可以是特定于类的函数。

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 及其“特权”对应物之间的理论区别,请查看此演示文稿

于 2014-02-11T19:29:19.357 回答
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
于 2014-02-12T15:00:01.470 回答
0

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

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

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

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

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

因此,已经处于语言核心级别的 JavaScript 确实提供了一种基于函数的 Mixin 模式,它比任何可用的extend(s)mixin 实现都更强大,因为它免费提供委托,并且能够传递几乎每个被指责的助手都缺乏的状态,除非付出努力以一种相当迂回的方式再次实现此功能(或者直言不讳地说是向后)。

伯吉解释已经赢得了赏金。在他的回答的最后一段中,有一个指向我的资源的链接,该链接在进行提到的演讲后 3 个月就已经过时了。由于没有足够的声望点,我无法直接评论他的回答。为此,我现在将借此机会指出我个人研究和理解的最新状态»JavaScript 的许多才能概括面向角色的编程方法,如 Traits 和 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 的基础,这些Traits依赖于“代理” Mixin 提供的冲突解决功能,不会将此功能公开。

为了不结束那个理论,将会有一个“现实世界的例子”,Queue用各种完全崇拜DRY方法的可重用 mixin 组成一个模块。它还应该回答 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; }

于 2014-12-25T00:20:51.490 回答