Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending JavaScript Namespace (IIFE)

My namespace is written using an immediately-invoked function expression (IIFE). My basic structure looks like:

(function ($, MyObjectName, undefined) {
    "use strict";

    MyObjectName.publicFunction = function () {
        privateFunction();
    };

    var privateFunction = function () {
        document.write('hello');
    };

}(jQuery, window.MyObjectName = window.MyObjectName || {}));

// call a public method

MyObjectName.publicFunction();

Now I want to extend this namespace with another publicly-accessible object. From here, I am executing another IIFE from within my current namespace. See below.

(function ($, MyObjectName, undefined) {
    "use strict";

    var ExtendedObject = (function ($, ExtendedObject, undefined) {
        "use strict";

        ExtendedObject.publicFunction = function () {
            privateFunction();
        };

        var privateFunction = function () {
            document.write('<br>hello again');
        };

    }(jQuery, window.MyObjectName.ExtendedObject = window.MyObjectName.ExtendedObject || {}));

}(jQuery, window.MyObjectName = window.MyObjectName || {}));

// call a public method from the extended namespace

MyObjectName.ExtendedObject.publicFunction();

Is the 'correct' way of doing this? If not, how can I achieve this?

like image 451
Sean Avatar asked Oct 21 '22 14:10

Sean


1 Answers

An IIFE is used to contain the scope of variables. Dependencies are passed in during the invocation just for neatness, as the function body does actually see them anyway. It helps keep your code neat and is a recognisable pattern.

However, once inside your IIFE you should cease referring to outside dependencies other than via your parameter names. Otherwise the effort of neatness is wasted.

Therefore when building nested namespaces/objects using this pattern you do not need to repeat the entire pattern.

Instead, simply assign your ExtendedObject to MyObjectName and re-use any of the arguments from the outer IIFE (e.g. $).

(function ($, MyObjectName, undefined) {
    "use strict";

    MyObjectName.ExtendedObject = (function () {
        "use strict";

        var exports = {};

        exports.publicFunction = function () {
            privateFunction();
        };

        var privateFunction = function () {
            document.write('<br>hello again');
        };

        return exports;
    }());

}(jQuery, window.MyObjectName = window.MyObjectName || {}));

Because you still want to have private and public methods on your ExtendedObject I am still using an IIFE but have converted it to use a variant of the revealing module pattern, whereby it builds an object of exported (public) functionality and returns that - as opposed to selectively attaching public methods to a passed-in object. My 'variant' is to use the exports variable which makes it clearer as you read the code which methods will be public, and avoids the error-prone repetition of those member names at the end.

If you wish to use JQuery or any features of MyObjectName inside ExtendedObject then you can do so, but do not prefix MyObjectName with window. as that reintroduces the dependency that you've already gone to lengths to avoid!

Going to these lengths to achieve member privacy is going out of fashion. The official JavaScript class syntax doesn't provide for them and building them oneself in the constructor looks decidedly clunky.

You seem keen to follow best practice and keep your code neat and your object exposed area meaningful. Is that to limit the hackability of your code in the hands of your end-users or to ensure it's not mis-used at design-time? If the latter then I'd recommend you take a look at TypeScript as that will allow you to code as if JS was strongly-typed and had private members. However it will transpile to vanilla JS with everything public, so it's not an ideal solution if you intended for the privacy to extend to your end-users.

Hope that helps.

like image 87
Raith Avatar answered Oct 23 '22 20:10

Raith