0

I have been practicing with JavaScript module pattern with proper Name-spacing. So basically I declare namespaces and each namespace has certain modules encapsulated in it. Here is what I have written so far. The code has been commented properly.

// namespace has been defined somewhere not to worry that it will be undefined
if (NAMESPACE == null || typeof (NAMESPACE) == 'undefined') {
    NAMESPACE = {};

    var id = function (id) {
        var _all_ids = {};
        var persona = {};
        var _id = id; // _id is a local variable that stores the argument

        var getId = function () { //this function returns the local private variable
            return _id;
        }

        persona.getId = getId;
        var _closed = false;

        var close = function () {
            delete _all_ids[getId()];
            this._closed = true;
        }

        persona.close = close;

        return persona; // persona is an object that has two properties `getId` and `close`. Both these are functiona
    }

    NAMESPACE['id'] = id; // so basically this will become NAMESPACE.id.getId or NAMESPACE.id.close
} 

I have fully commented tis code for anyone to understand. It declares a simple namespace and then adds a module in it. The module of course is using encapsulation.

One of my instructors suggest that this code has a basic flaw standard or otherwise. I can't figure this out although the code runs fine.

Is it good enough standard wise? Or am I doing it totally wrong?

Talha Masood
  • 993
  • 1
  • 7
  • 22
  • Is this the full module? Usually a module is encapsulated within an iife – Daniel Lizik Aug 27 '15 at 14:28
  • @daniel_L yes this is the full module -- and immediate function was declared in as well but I did not include it in the code here. Its just an example on how I am trying to create a namespace. – Talha Masood Aug 27 '15 at 14:31
  • Your setting `NAMESPACE['id'] = id`. This will make your namespace have the function behind `var id` instead of the module interface, since you didn't run that function yet. Better would be to return the persona object that is your interface for the module, and have the NAMESPACE decide where the module goes. This way you can reuse the module without depending on that your namespace will have all modules at its top level scope. – Shilly Aug 27 '15 at 14:38
  • @shilly interesting! But incase you noticed I am returning the `persona object` in the end. And `id` returns the persona object so I am essentially returning that anyway. – Talha Masood Aug 27 '15 at 14:42
  • 1
    @Talha, only if you go `NAMESPACE['id'] = id();` As written, you set the namespace.id to the function id, not to the object that function returns. – Shilly Aug 27 '15 at 14:44
  • Stylistically, you can probably write the id function a bit better as well. At the moment you also have an issue where var _closed is scoped to the id function, but you still return this._closed, while persona has no _closed property. `var id = function ( id ) { var _all_ids = {}, _id = id, _closed = false; return { 'getID' : function () { return _id; }, 'close' : function () { delete _all_ids[_id]; _closed = true; } }; }` – Shilly Aug 27 '15 at 14:50
  • @Shilly I think you are right. It works now when I use id(). See my basic jsfiddle here => https://jsfiddle.net/2g75ns5s/ – Talha Masood Aug 27 '15 at 14:53

4 Answers4

1

I've expanded the example a little bit to show the principles.

// NAMESPACE with install/define method, like when using require.js
var NAMESPACE = (function ( doc, win ) {
    var _modules = {};
    return {
        'install' : function ( name, definition ) {
            if (!_modules.hasOwnProperty(name)) {
                _modules[name] = definition;
                this[name] = _modules[name];
            }
            else throw new Error('Module ' + name + ' is already installed.');
        }
    };
}( document, window ));
// Your actual module to use in the NAMESPACE
NAMESPACE.install('id', (function ( id ) {
    var _all_ids = {}, 
        _id = id, 
        _closed = false; 
    return { 
        'getID' : function () { 
            return _id; 
        }, 
        'close' : function () { 
            delete _all_ids[_id];
            _closed = true; 
        } 
    }; 
}( 'someDefaultID' )));

// NAMESPACE as an object we pass around
var NAMESPACE = {};
(function ( NAMESPACE ) {
    var id,
        NAMESPACE = NAMESPACE;
    if (NAMESPACE == null || typeof (NAMESPACE) == 'undefined') {
        NAMESPACE = {};
        window.NAMESPACE = NAMESPACE;
    }
    id = (function ( id ) {
        var _all_ids = {}, 
            _id = id, 
            _closed = false; 
        return { 
            'getID' : function () { 
                return _id; 
            }, 
            'close' : function () { 
                delete _all_ids[_id];
                _closed = true; 
            } 
        }; 
    }( 'someDefaultID' ));
    NAMESPACE['id'] = id;
}( window.NAMESPACE ))
Shilly
  • 8,511
  • 1
  • 18
  • 24
0

Why not put the functions inside the namespace?

NAMESPACE = {
 id: function (id) {
  // The id function in here
 }
}
Lekoaf
  • 897
  • 7
  • 11
0

The point of namespacing is to avoid polluting the global scope. In your code,

if (NAMESPACE == null || typeof (NAMESPACE) == 'undefined') {
    NAMESPACE = {};

    var id = function (id) {
        // ...
    }

    NAMESPACE['id'] = id; // so basically this will become NAMESPACE.id.getId or NAMESPACE.id.close
}

id is now defined in the global scope as well as on your namespace. This defeats the purpose of using namespaces.

A simple plain JS way of achieving the same objective of defining your namespace without polluting global scope is by rearranging

if (NAMESPACE == null || typeof (NAMESPACE) == 'undefined') {
    NAMESPACE = {};

    NAMESPACE['id'] = function (id) {
        // ...
    }
}

or

if (NAMESPACE == null || typeof (NAMESPACE) == 'undefined') {
    NAMESPACE = {
        id: function (id) {
        // ...
    }

}

as @Lekoaf suggested.

However, the modern way of avoiding the global pollution is to simply enclose all your code inside an IIFE. While there's there's more than one way to enclose your code in an IIFE, the simplest way is to just drop it in as is

(function(){ // Beginning of IIFE
if (NAMESPACE == null || typeof (NAMESPACE) == 'undefined') {
    NAMESPACE = {};

    var id = function (id) {
        var _all_ids = {};
        var persona = {};
        var _id = id; // _id is a local variable that stores the argument

        var getId = function () { //this function returns the local private variable
            return _id;
        }

        persona.getId = getId;
        var _closed = false;

        var close = function () {
            delete _all_ids[getId()];
            this._closed = true;
        }

        persona.close = close;

        return persona; // persona is an object that has two properties `getId` and `close`. Both these are functiona
    }

    NAMESPACE['id'] = id; // so basically this will become NAMESPACE.id.getId or NAMESPACE.id.close
} 
})(); // End of IIFE
I-Lin Kuo
  • 3,220
  • 2
  • 18
  • 25
  • I would agree to you partially, especially to your last code snippet. Yes we can define global variables within a module and use those within the same module as well. Also the private methods and variables can be revealed by revealing API. – Talha Masood Aug 27 '15 at 17:54
0

You can use an augmenting module pattern in order to avoid overriding your global module and its methods.

//fooModule does not exist, so it is defined as an empty object
var fooModule = (function(app, name, definition) {
    if (!app[name]) app[name] = definition;
    else throw new Error("this method has already been defined");
    return app;
})(fooModule || {}, "foobar", function() {
    console.log("foobar");
});

//fooModule now exists, so we use the pre-existing object instead of an empty one
var fooModule = (function(app, name, defintion) {
    if (!app[name]) app[namepace] = definition;
    else throw new Error("this method has already been defined");
    return app;
})(fooModule || {}, "barfoo", function() {
    console.log("barfoo");
});

//throws an error because barfoo has already been set to fooModule
var fooModule = (function(app, name, defintion) {
    if (!app[name]) app[namepace] = definition;
    else throw new Error("this method has already been defined");
    return app;
})(fooModule || {}, "barfoo", function() {
    console.log("should throw error");
});

fooModule.foobar(); //"foobar"
fooModule.barfoo(); //"barfoo"    

The important part is myModule || {}. If the module exists, use it, if it doesn't, create the module.

This only addresses methods on a global object. Have you read this article? Probably the best I've read on module patterns.

Daniel Lizik
  • 3,058
  • 2
  • 20
  • 42