javascriptdesign-patternsmixinsmodule-pattern

Javascript mixins when using the module pattern


I've been using the module pattern for a while, but recently have started wanting to mix in functions and properties into them to increase code re-use. I've read some good resources on the subject, but still am a bit uncertain as to the best approach. Here is a module:

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

    //"constructor"
    function module() {}

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

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

    return module;
}

And here is a mixin object:

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

Ideally, I'd like to mix-in some methods from other objects as private methods and some as public methods, so that I could call some "extend" function, with a param as "private"/"public". So, that

mixin(myModule, myMixin, "private");

makes the myMixin methods available within myModule by just calling mixinMethod1() and have correct scope, and:

mixin(myModule, myMixin, "public");

makes the myMixin methods available within myModule by calling module.mixinMethod1() and have correct scope

I've tried using a method that copies properties from one prototype to another, I've tried the underscore extend method to copy properties of the object from one to to the other, and various things in between. I think I'm a bit turned around regarding scope and prototypes at this point, and would love some direction as to how best to do mixins like this when using the module pattern. Note that it doesn't matter what the object myMixin looks like (whether adding functions to the prototype, or a module itself), I'm just trying to figure out some way to make it work.

Thank!


Solution

  • So that [some code] makes the myMixin methods available within myModule by just calling mixinMethod1() and have correct scope

    That's impossible. You cannot modify a scope by calling a function, especially not from outside. See also Is it possible to import variables in JavaScript? for the design reasons of that.

    So, what can you do?

    From outside the module

    Nothing to the private scope(s) of module functions. And you cannot use the private functions of the module, obviously. You can extend its prototype with methods (which is the most common), you can even decorate its constructor function. Within those, you can use your own private functions, either completely static ones or class-specific ones.

    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)
    

    From within the module

    Using the mixin in the module code itself can allow for much greater flexibility.

    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(){…};
                …
            }
        };
    })();
    

    With such an interface, it becomes easy to construct a class that is using this as a mixin (not inheriting from it):

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

    However, the mixin will never have access to the private class variables, nor can it present a private, class-specific API. Still, we can use dependency injection to provide that access explicitly (and having a mixin factory in effect):

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

    Not all techniques shown above need to be used in every mixin, just choose the ones you need. Not all possible techniques are shown in the above examples, also :-) The mixin pattern can be applied onto a plain module or inside its declaration as well, the above examples have only shown classes with prototypes.

    For a few good examples, and a theoretical distinction between (stateless) Traits, (stateful) Mixins and their "privileged" counterparts, have a look at this presentation.