JavaScript Modules, Closures and Scope

I am using the following closure pattern to modularise my code:

(function(root) {
  // MODULE CODE HERE

  if (typeof module !== 'undefined' && module.exports) { // CommonJS 
    /* var dependencies = require(...) */
    module.exports = myModule;
  } else if (typeof define !== 'undefined' && define.amd) { // AMD
    /* var dependencies...; */
    define([/* dependencies */], function(/* dependencies */) {
      /* Assign closure level vars to respective arguments */
      return myModule;
    });
  } else {
    // Dependencies??
    root.myModule = myModule;
  }
})(this);

i.e., We use feature detection to support CommonJS modules (e.g., node.js), AMD or basic global namespace instantiation.

This works fine in node.js; I haven't tested the AMD pattern yet, as I'm still reading up on it (See Edit 2: AMD exhibits the exact same effect); but it fails in the browser if the module has any dependencies. That is, say, if myModule references something that is defined in a different module: For example, say if I had super.js and child.js with respective module definitions, as above, where super.js creates a function called root.super (root === window in the browser), if child.js tries to do super(), I will get something like super is not a function.

What's going on here?

To try to fix it, I changed the order in which super.js and child.js are loaded in <script> elements: No luck. Then I tried forcing child.js to load when the document is ready, using jQuery:

$(document).ready(function() {
  $.getScript('child.js', function() {
    // Do stuff with child, which calls super
  });
});

...again, same problem. However, in both cases, if I enter the console, super is available and defined as I'm expecting.

Why is super in child.js presumably from a different (i.e., not the global) scope?


I should add, if I remove the dependency injection bit in the CommonJS export, it fails in node.js with the same error (if there are any dependants).


EDIT @Amberlamps' answer solved the problem, but it didn't answer the question as to why this occurs. My module pattern is now:

(function(root) {
  // MODULE CODE HERE

  if (typeof module !== 'undefined' && module.exports) { // CommonJS 
    /* var dependencies = require(...) */
    module.exports = myModule;
  } else if (typeof define !== 'undefined' && define.amd) { // AMD
    /* var dependencies...; */
    define([/* dependencies */], function(/* dependencies */) {
      /* Assign closure level vars to respective arguments */
      return myModule;
    });
  } else {
    if (root.hasOwnProperty(/* dependencies */)) {
      /* var dependencies = root... */
      root.myModule = myModule;
    }
  }
})(this);

This keeps dependants with a common name, across environments. However, the question remains: Why is the global object not available within the closure's scope?


EDIT 2 I've been experimenting with RequireJS and AMD and have corrected my code, above, so that the AMDs work. Exactly the same thing happens in this case, too: You have to explicitly assign the global object to a variable within the closure for it to be available within said closure...

Answers:

Answer

This pattern works just fine. If you actually tested it with a function called super and calling it via super(), you might ran into an error, because super is a reserved word. Following code works fine:

(function(root) {

    root.super = function() {

        console.log("hello");

    };

}) (window);

(function(root) {

    root.super();

}) (window);

You can call your function using window.super(). super() however will result in an error.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.