Skip to content

IIFE Parentheses #21

Closed
Closed

Description

@jcutrell

Could this:

(function() {
  console.log('Welcome to the Internet. Please follow me.');
})();

Be this? :

// Crockford's preference - parens on the inside
(function() {
  console.log('Welcome to the Internet. Please follow me.');
}());

Activity

  1. reissbaker commented on Nov 8, 2012

    @reissbaker
    Contributor

    For modules we've decided on this:

    !function() {
      console.log('Welcome to the Internet. Please follow me.');
    }();

    ...But I have no particular preference on where parens should go for IIFEs that aren't also modules. Usually they're not very useful -- if it needs to be hidden, make it its own module -- but in the cases where they are it doesn't really matter to me. Both seem equally readable.

    Is there enough of a difference to make this worth codifying?

  2. hshoff commented on Nov 8, 2012

    @hshoff
    Member

    If you don't care about the return value of the IIFE, it could also be any of the following:

    !function(){}();  // => true
    ~function(){}(); // => -1
    +function(){}(); // => NaN
    -function(){}();  // => NaN

    Let's explore this a bit more.

    // module1.js
    (function() {
      console.log('module1');
    })();
    
    // module2.js
    (function() {
      console.log('module2');
    })();
    // production.min.js
    (function(){console.log('module1');})();(function(){console.log('module2');})();
    // => module1
    // => module2
    
    // crockford production.min.js
    (function(){console.log('module1');}());(function(){console.log('module2');}());
    // => module1
    // => module2

    Both work the same. It starts to get interesting when one of the modules is missing a trailing semicolon:

    // problem
    (function(){console.log('module1');})()(function(){console.log('module2');})();
    // => module1
    // => TypeError: undefined is not a function
    
    // crockford problem
    (function(){console.log('module1');}())(function(){console.log('module2');}());
    // => module1
    // => module2
    // => TypeError: undefined is not a function

    With a missing semicolon, each set of parens is trying to immediately-invoke the preceding expression. That would be the return value of the preceding IIFE.

    (function(){ return undefined; })()();
    // => TypeError: undefined is not a function
    
    // equivalent
    (undefined)();
    
    (function(){ return undefined; }())(); // crockford
    // => TypeError: undefined is not a function
    
    // equivalent
    (undefined());

    So the difference is when the TypeError happens. Let's check out what the arguments are up to. Note that console.log() returns undefined:

    // new module1.js
    (function() {
      console.log('module1');
    
      // return a function that logs it's
      // arguments when invoked
      return function(){
        console.log(arguments);
      };
    })()
    // new production.js
    // unminified problem for readability
    (function() {
      console.log('module1');
    
      // return a function that logs it's
      // arguments when invoked
      return function(){
        console.log(arguments);
      };
    })()(function() {
      console.log('module2');
    })();
    // => module1
    // => [function() {
    //      console.log('module2');
    //    }]
    // => TypeError: undefined is not a function
    
    // the first step is to invoke the first module
    (function(){console.log('module1');return function(){console.log(arguments)};})()
    // => module1
    
    // then it invokes the return value
    // with module2 as arguments
    (function(){ console.log(arguments) })(function() {console.log('module2');});
    // => [function() {
    //      console.log('module2');
    //    }]
    
    // then it tries to invoke the return value.
    // console.log returns undefined so:
    (undefined)();
    // => TypeError: undefined is not a function

    Now let's do that same example with the crockford way:

    // new crockford module1.js
    (function(){
      console.log('module1');
    
      // return a function that logs it's
      // arguments when invoked
      return function(){
        console.log(arguments);
      };
    }())
    // new production.js
    // unminified problem for readability
    (function(){
      console.log('module1');
    
      // return a function that logs it's
      // arguments when invoked
      return function(){
        console.log(arguments);
      };
    }())(function(){
      console.log('module2');
    }());
    // => module1
    // => module2
    // => [undefined]

    But wait, there's no TypeError here...

    // it invokes module1
    (function(){ console.log('module1'); return function(){ console.log(arguments); };}())
    // => module1
    
    //it invokes module2
    (function(){ console.log('module2'); }());
    // => module2
    
    // equivalent
    (function(){ console.log(arguments); })(undefined)
    // => [undefined]

    There's no TypeError because of the returned function. The returned function that logs the arguments is then getting invoked with the return value of module2, which is undefined.
    With that understanding, let's go back to the original example, where there was a TypeError:

    // crockford problem
    (function(){console.log('module1');}())(function(){console.log('module2');}());
    // => module1
    // => module2
    // => TypeError: undefined is not a function
    
    // it invokes module1
    (function(){ console.log('module1'); }())
    // => module1
    
    //it invokes module2
    (function(){ console.log('module2'); }());
    // => module2
    
    // equivalent
    (undefined(undefined))
    // => TypeError: undefined is not a function

    Conclusion
    The (function{})(); and (function(){}()); IIFEs can act differently in the missing semicolon situation.

    Use linter or a tool to make sure modules aren't missing trailing semicolons when working on modules.

    To be extra safe add a leading semicolon to the IIFE:

    // module1.js
    ;(function() {
      console.log('module1');
    })()
    
    // crockford module1.js
    ;(function(){
      console.log('module1');
    }())
    
    // module2.js
    ;(function() {
      console.log('module2');
    })();
    
    // crockford module2.js
    ;(function() {
      console.log('module2');
    }());
    
    // production.min.js
    ;(function(){ console.log('module1'); })();(function(){ console.log('module2'); })();
    // => module1
    // => module2
    
    // crockford production.min.js
    ;(function(){ console.log('module1'); }());(function(){ console.log('module2'); }());
    // => module1
    // => module2

    Hope that helps!

  3. jcutrell commented on Nov 8, 2012

    @jcutrell
    Author

    A fantastically thorough explanation.

  4. egonyuri commented on Nov 16, 2012

    @egonyuri

    Incredible explanation.

  5. mikejoyceio commented on Mar 22, 2015

    @mikejoyceio

    Great explanation. Thanks.

  6. jaan commented on Mar 22, 2015

    @jaan

    Good Explanation @hshoff

  7. freeethy commented on May 6, 2015

    @freeethy

    Great explanation @hshoff . Thanks.

  8. xiehongyang commented on Apr 21, 2016

    @xiehongyang

    Cool! help me a lot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions