javascriptnamespacesscopeiife

Scope with IIFE in Javascript


I'm using a script that use jsonP to share cross domain information. It works well, but I need to put it inside an IIFE.

var domain = "http://example.com/";
var myObj = {
    recupData : function(data){
        if (data.id) {
            console.log(data.id);
        }
    },
    scriptTag : function() {
        var siteOrigin = domain+"check?q=myObj.recupData",
            script = document.createElement('script');
        script.type = 'text/javascript';
        script.async = true;
        script.src = siteOrigin;
        document.getElementsByTagName('HEAD')[0].appendChild(script);
    }
}
myObj.scriptTag();

That works! (I just took a very little part of my global script, just to show you the structure, so if there is any syntax error there, that's not the point).

But when I put this code in an IIFE, a self invoked function, I get some trouble.

(function(){
var domain = "http://example.com/";
var myObj = {
    recupData : function(data){
        if (data.id) {
            console.log(data.id);
        }
    },
    scriptTag : function() {
        var siteOrigin = domain+"check?q=myObj.recupData",
            script = document.createElement('script');
        script.type = 'text/javascript';
        script.async = true;
        script.src = siteOrigin;
        document.getElementsByTagName('HEAD')[0].appendChild(script);
    }
}
myObj.scriptTag();
})();

I get the error myObj is not defined, the error comes from the scriptTag method and I really don't understand why I can't access to this method until I have added an IIFE, it shouldn't change anything, It just avoids to pollute the global namespace. I think it's just a context problem, but I need an explanation.


Solution

  • The motivation for using IIFEs is that your variable names don't "leak" into the global scope. However, when you're using JSONP, you are obligated to expose at least one variable to the global scope so the loaded script can call it (or one of its methods). This loaded script is inserted as a separate script tag into the head, in a completely different scope and thus you can only share variables with it through the global scope.

    For example, imagine that your call to http://mydomain.com/check?q=myObj.recupData produces a response like:

    myObj.recupData({"id":123,"more":"stuff"})
    

    This is being loaded inside a separate script tag, as if you'd written:

    <script type="text/javascript">
    myObj.recupData({"id":123,"more":"stuff"})
    </script>
    

    Clearly, if this call ought to work, myObj needs to be in the global scope so you should move its declaration outside of the IIFE, or explicitly register it on the window object:

    // Global declaration
    var myObj;
    (function(){
        var domain = "http://mydomain.com/";
        myObj = { ... };
        myObj.scriptTag();
    })();
    
    // Registering on window
    (function(){
        var domain = "http://mydomain.com/";
        // Also get it as a local variable
        // for a minor scope lookup optimization
        var myObj = window.myObj = { ... };
        myObj.scriptTag();
    })();
    

    The second option may be more interesting since it will always end up in the global scope, no matter where you place the snippet. If for some reason you end up nesting the first snippet inside another IIFE, you'd need to remember to move the myObj declaration as well, which can be cumbersome.