Search code examples
javascriptiife

Unexpected sequence of IIFE execution


Am a bit of a self-taught JS hack and trying to be a little more formal as I have to build a better-behaved JS application that is name-spaced and a better JS citizen. I would appreciate some pointers on what I am doing wrong here.

The idea is to use the first closure to construct a namespace, then in the second I add a method a 'class' constructor to the namespace object. The I call a method on the sub.

(function() {
    'use strict';
    /**
     * @namespace ProjectX
     */

    var ProjectX = {

        attr1: 'I am inside ProjectX'
        
    }
    window.ProjectX = ProjectX;

})()

//console.log(ProjectX.attr1); // When this line is uncommented the code will output 'foobar' as expected?

(function() {
    'use strict';

    ProjectX.subObj = function(val) {
        this.name = val;
        return this;
    };
})();


var subObj = new ProjectX.subObj('Foobar');
console.log(subObj.name);

I am expecting to see 'Foobar' in the console log. However this yields the following result in the console:

{ "message": "Uncaught TypeError: (intermediate value)(intermediate value)(intermediate value)(...) is not a function", "filename": "http://stacksnippets.net/js", "lineno": 03, "colno": 1 }

When I read this code I expect that the two IIFE's will execute in sequence, the first bringing ProjectX to life, then the second adding a further constructor to ProjectX that will return a subObj. Further, if the console.log((ProjectX.attr1) at line 16 is un-commented, the code will run as I anticipated. This seems to suggest that the ProjectX entity is not 'present' until I have referenced it. This contradicts my understanding of IIFE's, which is that they run immediately, and anyway, ProjectX is not a constructor - it is a global variable.

In researching I found function inside of IIFE not recognized which appears to be similar territory but is related to binding an event to the DOM and not a parallel to my query.


Solution

  • You are missing a semicolon after first IIFE, which becomes relevant when the console.log with its semicolon is removed. You can search for the cases where semicolons are required in JS

    Without semicolon, your code can be interpreted as:

    (function() {
        var ProjectX = {
            attr1: 'I am inside ProjectX'
        }
        window.ProjectX = ProjectX;
    })()(function() {
        ProjectX.subObj = function(val) {
            this.name = val;
            return this;
        };
    })();
    

    Check the following function in the format (IIFE)()(), this works because IIFE returns a function, which is invoked with ('Hey')

        (function() {
            return function(msg){console.log(msg)}
        })()('Hey')

    Without semicolon, javascript expects that your first IIFE returns a function, which is not the case with your code so it throws a Type error saying that it is expecting a function but got undefined instead. Your code eventually evaluates to:

    undefined(function() {
        ProjectX.subObj = function(val) {
            this.name = val;
            return this;
        };
    })();
    

    or more simplified as

    undefined(<a function as parameter>)()
    

    Which then throws the error that a function is expected here instead of undefined.

    Read about higher order functions in javascript to get a better idea of this syntax and to what your code is being ambiguous against.