0
\$\begingroup\$

I've recently been learning about the revealing module pattern, and it seems like a really good way to structure code in many cases. However, I find myself wanting to use it like a class, so I'm trying to modify it to behave in the following ways:

  • Instances get created from the module (doesn't just immediately return its own object)
  • The module can take arguments and use them like a constructor (I don't see this in many revealing module examples)
  • The module can be extended like a class; extending modules inherit its public properties/methods and can share arguments/parameters

I seem to have achieved this by using just a function expression instead of an IIFE, creating an instance of the parent within the extending module, and passing the extending module's arguments into the parent instance. Here is the full working pattern I've come up with:

var Module = function({text = 'no text given'} = {}) {
    var private = {};
    private.text = text;
    private.logText = function() {
        console.log(private.text);
    };

    var public = {};
    public.logText = private.logText;

    return public;
};


var Extended = function({extraText = 'no extra text given'} = {}) {
    var parent = Module(arguments[0]);
    // Note: I chose to use an object as a parameter in this snippet, 
    // but you could also just use regular parameters and pass in
    // arguments instead of arguments[0]

    var private = {};
    private.extraText = extraText;
    private.alertText = function() {
        alert(private.extraText);
    }
  
    var public = {};
    public.logText = parent.logText;
    public.alertText = private.alertText;

    return public;
};


// TEST INSTANCES

var testModule = Module({ text: "I'm in the console." });
testModule.logText(); // "I'm in the console."

var testExt = Extended({ extraText: "I'm in an alert." });
testExt.logText(); // "no text given"
testExt.alertText(); // alerts "I'm in an alert."

var anotherExt = Extended({
  text: "I'm in the console.",
  extraText: "I'm in an alert."
});
anotherExt.logText(); // "I'm in the console."
anotherExt.alertText(); // alerts "I'm in an alert."

Now it basically behaves as a class like I was wanting - but I'm wondering what limitations/shortcomings there might be that I'm not seeing. Is the revealing module pattern ever used this way?

\$\endgroup\$
  • \$\begingroup\$ I am trying to understand the downvote. Is the code in my question too hypothetical, or is the question bad in some other way? \$\endgroup\$ – ReeseyCup Feb 8 '18 at 0:53
1
\$\begingroup\$

Review

This is a code review so I will point out some style problems first

Function expressions V statements

There are several ways to ways you can declare a named function.

  1. As a statement. function functionName() { }
  2. As an expression. const functionName = function() { }; Note the semicolon

The most important difference between the two is that function statements are hoisted and available in all part of the scope they are declared in. Function expressions are not, even if you declare them as a var (which is hoisted) it remains undefined until it is assigned a value.

Function declared as statements are thus the safest form of function declaration and should be preferred over expression declarations.

testA(); // no error and calls testA
testB(); // throws an error ReferenceError: testB is not defined
testC(); // throws and error TypeError: testC is not a function

function testA(){};
const testB = function(){};
var testC = function(){}; 

const, var and let

When declaring variables use the appropriate type. const is the preferred type, unless you need to modify the value then use var. If you wish to use let in preference to var that is up to you, but be warned that let is not hoisted.

Avoid reserved words.

There is a set of words that are reserved and represent existing, or possible future tokens. Two of these are public, and private you should avoid using reserved words.

There are also many context specific words that you should try to avoid unless you are sure you are not overwriting an existing global property. For example parent, though you safely use it, I do not know if that is just luck or knowledge that parent is a global scope object AKA window.parent and you are in function scope thus not overwriting the global.

Anti pattern

I consider this as an anti pattern

var Extended = function({extraText = 'no extra text given'} = {}) {
   var parent = Module(arguments[0]);

I am not sure if you are aware that if you call

 Extended(); // without an argument

Then arguments[0] is undefined, and arguments.length === 0 but the destructured argument named extraText has the value "no extra text given"

If you did this in purpose may I suggest that you use an alternative...

const Extended = function(info = {}) {         
    const parent = Module(info);
    info = {extraText : "no extra text given", ...info};

...as it is much clearer what the intent is.

Use quotes not apostrophes

To avoid confusion with template string declarations delimited by Back Quote try to avoid using Apostrophes (AKA single quite)

Don't duplicate variables

The object Module creates the object private and assign the property text the value of the argument text. This is just a duplication as the only place you use text is in the function it is available as the argument

Design

You are on the correct path to using JavaScript to create private (protected) and public states. Though there is no need to create the private objects as you can use the function scope to hold the private state, including functions

Simple example of a object with both public and private states, using getters and setters to protect type (count as a Number), and access to private state from private and public scopes (eg function 'show')

function MyObj(text = "Nothing to see here."){

     if (typeof text !== "string") {  text = "Wrong type." }

     // private variables
     var count = 0;  // Type safe from outside

     // private functions declared here or in the API below
     function show(){
         console.log(text);
         count += 1;
     }

     // public API also accessible from private scope
     const API = {
         get count() { return count  },
         set count(c) {  // only set if a number to keep type safe state
             if (!isNaN(c)) { count = Number(c) }
         }
         show,  // public access to private function

        // OR add the function here
         show() {
            console.log(text);  // still have private access
            count += 1;
         }
     }

     // internal access 
     setTimeout(show, 1000); // via private function

     // or via public API
     setTimeout(API.show, 2000); 


     return API;
}

You can nest these objects creating additional levels of state security. You can also create several public objects that share a single private state,

A rewrite

As your code does nothing with its private states there was not much to go by. The Design section above would be more appropriate for what you may be after.

The following does the same as your code with considerably less noise.

function Module({text = 'no text given'} = {}) {
    function logText() { console.log(text) };  // private function 
    return { logText };
}

function Extended(info = {}) {
    info = {extraText : "no extra text given", ...info};
    function alertText() { alert(info.extraText) }
    return { ...Module(info), alertText };
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.