javascriptmodulescopeglobal-variablesstatic-classes

Variables in a Javascript module visible outside of it?


To start, I'm coming from a .NET world, where there are static classes (C#, also known as modules in VB) and instance classes - those you can instantiate.

This question is about Javascript, where I'm trying to recreate the pattern I already know and make a module / static class. Here is the code:

var MyModule = {
    variable1: 0,
    variable2: 1,
    method1: function() {
        //code goes here
    },
    method2: function() {
        //mode code goes here
    },
    method3: function() {
        //and some more just to prove the pattern        
    }
}

method1(); //error - no such method - ok, good, I expect that
variable1 = 5; //silently assigns a variable inside MyModule to 5 ?

Please explain why methods declared inside a namespace are not visible, but variables are ? Also, is there a way to prevent those variables from being visible outside of MyModule, even in the same script ?


Solution

  • I will start by trying to address some misconceptions - your module is actually a plain JavaScript object initialised using a literal. An object does not hide any information - JavaScript does not have private and public scope the same way as, say Java does. Furthermore, the only way to access properties of an object is to access them through it

    method1();
    

    This fails because you are trying to get a variable called method1 and execute it. This does not exist, hence the error. What you can do, however, is MyModule.method1(). This is because MyModule is an actual object. In Java, this would be similar to having Object MyModule = new Object(). You can thus call methods on it, like MyModule.toString() - the only "unconventional" thing, is that the instance is named with a capital letter - variables in Java and JavaScript normally start with lower case but only by convention - there is no language feature that mandates it.

    Next, there is this line:

    variable1 = 5; //silently assigns a variable inside MyModule to 5 ?
    

    To answer the comment on the line - no, it doesn't. The above line is equivalent to doing window.variable1 = 5. What you have there is called an "implied global". The name is fairly self descriptive, but just to clarify window.foo = 42 is an explicit global foo = 42 is implied because it falls back to the window object. However, var foo = 42 is not a global - the var keyword does not attach to or use the global window object. If you use the "use strict" directive you will cause the JavaScript engine to throw an error when it encounters an implied global.

    Now, with these misconceptions aside, there is another minor one that I will merely gloss over but it's actually a major thing - MyModule.method1 and so on are functions rather than methods. A method is an Object Oriented terminology for a subroutine that also carries an implied state, which is the object it's assigned to. A function, however, is a free floating executable code that does not intrinsically "belong" to an object. The distinction may seem rather academic, but it has a big impact. However, I'll ask you to trust me on this one, as this is too broad a topic to cover here.

    So, that's what's wrong. How do we make it right? First, on scopes, as that is needed for the rest of it:

    JavaScript has two scopes - global and functional. Here is what this means, exactly:

    var foo = 42;
    
    function bar(input) {
        var foo = input;
        return foo;
    }
    
    console.log(bar(5)); //5
    console.log(foo); //42
    

    We have too variables called foo - one inside a function one not. Assigning one does not change the other - it's because they are in different scopes. That's thanks to the var keyword - if that was omitted inside the function, then we would be accessing the same thing

    var foo = 42;
    
    function bar(input) {
        foo = input;
        return foo;
    }
    
    console.log(bar(5)); //5
    console.log(foo); //5
    

    This is because the inner foo is not declared to be in the functional scope, so it accesses the outer (and with no outer - the window.foo property).

    That's simple enough. However, there is a hidden aspect here that is not immediately obvious yet it bears mentioning: there are only two scopes. Here is what I mean

    function bar(condition) {
        var foo = 5;
        if (condition) {
            var foo = 42;
        }
        return foo;
    }
    
    console.log( bar(false) ); //5
    console.log( bar(true) ); //42
    

    So, even though we seemingly declare a new foo property inside the if statement that's actually the same one as the first one. Other languages have a block scope inside { } so, variables declared there, stay there. Not true in JavaScript. Easy to miss, so it's important to note.

    Now, onto your problem. There are excellent resources about the JavaScript module pattern, to mention but a few: Addy Osmani's "Learning JavaScript Design Patterns" book has a very good description, Todd Motto also has, what I believe is, a good article for beginners while the Adequately Good post goes more in-depth yet it's still accessible. They all cover the Module pattern. You will actually found many Module patterns - in those resources and around the web. What the Module allows you to do is have something similar to public and private from other languages. I will not try to explain it, since I think other people have done it way better than I can. However, I will just do a very brief explanation what a Module is. Again, other resources can cover this better.

    A Module leverages a JavaScript feature called Closure more information here and here (one of the most upvoted Stack Overflow questions). Remember how I said, there are two scopes in JavaScript? A closure, to put it simply, provides a functional scope. Here is how it looks like:

     (function() { /* your code goes here */ })()
    

    To dissect this briefly you have a function with your code an that is enclosed in (/* function goes here */)(). In effect what this does is that executes the function so everything inside is put into functional scope. It is very powerful, however, and here is an example

    var closure = (function() { 
        var privateVar = "secret";
        return {
           publicVar: 42,
           getPrivateVar() { return privateVar; }
        }
    })()
    
    console.log(closure.publicVar); //42
    console.log(closure.getPrivateVar()); //"secret"
    //let's change this
    closure.publicVar = 5;
    closure.privateVar = "changed";
    
    console.log(closure.publicVar); //5
    console.log(closure.getPrivateVar()); //"secret"
    

    And just like that we have (something resembling) a private field. Again, var privateVar will stay "bottled up" inside the function scope and would not be accessible outside. Incidentally, this is an example of the Module pattern. Instead of var closure I could have called it myModule. It is a rather simplistic take on it but, here is the thing - it doesn't get more complicated. All the different Modules boil down to very similar things - you just expose data in different manners. Yet, the concept is similar - the "private" fields stay inside, and the "public" ones, you can access from elsewhere.

    This is long, but hopefully should cover the basics you need to get going.