Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IIFE Parentheses #21

Closed
jcutrell opened this issue Nov 8, 2012 · 8 comments
Closed

IIFE Parentheses #21

jcutrell opened this issue Nov 8, 2012 · 8 comments

Comments

@jcutrell
Copy link

jcutrell commented Nov 8, 2012

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.');
}());
@reissbaker
Copy link
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?

@hshoff
Copy link
Member

hshoff commented Nov 8, 2012

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!

@jcutrell
Copy link
Author

jcutrell commented Nov 8, 2012

A fantastically thorough explanation.

@hshoff hshoff closed this as completed Nov 15, 2012
@egonyuri
Copy link

Incredible explanation.

@mikejoyceio
Copy link

Great explanation. Thanks.

@jaan
Copy link

jaan commented Mar 22, 2015

Good Explanation @hshoff

@freeethy
Copy link

freeethy commented May 6, 2015

Great explanation @hshoff . Thanks.

ljharb added a commit that referenced this issue Jan 15, 2016
…de section.

There was lots of discussion [here](#21 (comment)), but now that we have both a modern build system and an eslint rule requiring terminating semicolons, the concerns with the “crockford” style no longer apply.
gilbox pushed a commit to gilbox/javascript that referenced this issue Mar 21, 2016
…de section.

There was lots of discussion [here](airbnb#21 (comment)), but now that we have both a modern build system and an eslint rule requiring terminating semicolons, the concerns with the “crockford” style no longer apply.
@xiehongyang
Copy link

Cool! help me a lot

jaylaw81 pushed a commit to appirio-digital/ads-best-practices that referenced this issue Sep 19, 2017
…de section.

There was lots of discussion [here](airbnb#21 (comment)), but now that we have both a modern build system and an eslint rule requiring terminating semicolons, the concerns with the “crockford” style no longer apply.
sensiblegame added a commit to sensiblegame/React-BNB that referenced this issue Oct 23, 2017
…de section.

There was lots of discussion [here](airbnb/javascript#21 (comment)), but now that we have both a modern build system and an eslint rule requiring terminating semicolons, the concerns with the “crockford” style no longer apply.
passionSeven added a commit to passionSeven/javascript that referenced this issue Jan 27, 2023
…de section.

There was lots of discussion [here](airbnb/javascript#21 (comment)), but now that we have both a modern build system and an eslint rule requiring terminating semicolons, the concerns with the “crockford” style no longer apply.
Binary-Ninja-007 added a commit to Binary-Ninja-007/JavaScript_Style_Guide that referenced this issue Aug 13, 2023
…de section.

There was lots of discussion [here](airbnb/javascript#21 (comment)), but now that we have both a modern build system and an eslint rule requiring terminating semicolons, the concerns with the “crockford” style no longer apply.
harry908nilson pushed a commit to marcolane/Javascriipt that referenced this issue Sep 1, 2023
…de section.

There was lots of discussion [here](airbnb/javascript#21 (comment)), but now that we have both a modern build system and an eslint rule requiring terminating semicolons, the concerns with the “crockford” style no longer apply.
noakosar515 added a commit to noakosar515/JavaScript that referenced this issue Sep 14, 2023
…de section.

There was lots of discussion [here](airbnb/javascript#21 (comment)), but now that we have both a modern build system and an eslint rule requiring terminating semicolons, the concerns with the “crockford” style no longer apply.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants