DEV Community

ELI5: Why use a function declaration, expression, or an IIFE in JavaScript?

Ryan Smith on February 23, 2019

I have seen functions in JavaScript declared in different ways. I have been trying to look up why, but answers that I have found only seem to state...
Collapse
 
somedood profile image
Basti Ortiz • Edited

Yes, you are correct that an IIFE creates its own scope. As a consequence of that, any variable declared inside an IIFE is only visible to its scope.

const outside = 'foo';

(function() {
  const inside = 'bar';

  console.log(outside); // 'foo'
  console.log(inside); // 'bar'
})();

console.log(outside); // 'foo'
console.log(inside); // ReferenceError
Enter fullscreen mode Exit fullscreen mode

In the olden days of JavaScript, we used IIFEs to avoid polluting the global namespace. Nowadays, we can just use block scopes to achieve the same thing. I'm not sure about the browser support, though, so take that with a grain of salt.

const outside = 'foo';

// This only works for block-scope variables (i. e. `const` and `let`).
{
  const inside = 'bar';

  console.log(outside); 'foo'
  console.log(inside); 'bar'
}

console.log(outside); // 'foo'
console.log(inside); // ReferenceError
Enter fullscreen mode Exit fullscreen mode

With that said, an IIFE is pretty much obsolete now that we can declare block-level scopes as such. However, IIFEs are still prevalent because of backwards-compatibility.

Upon your special request, I might write an article that goes more in-depth about each of the use cases you mentioned. I probably should. I hope my time allows it. It's quite an interesting topic. I'll be sure to mention you in my (future) article if so.

Collapse
 
jckuhl profile image
Jonathan Kuhl • Edited

I did find an interesting use case for an IIFE the other day.

I wanted an array of emojis. I got them from EmojiCopy and I copied them as a string. I didn't want however to write them all down one by one to create an array. What I needed instead was a function that is immediately invoked and turns my string into an array. What makes this difficult, is that I can't use split('') because an emoji is technically two characters, not one.

So I used an IIFE to create my array:

const faces = (() => {
    const faces='๐Ÿ˜€๐Ÿ˜ƒ๐Ÿ˜„๐Ÿ˜๐Ÿ˜†๐Ÿ˜…๐Ÿ˜‚๐Ÿคฃ๐Ÿ˜Š๐Ÿ˜‡๐Ÿ™‚๐Ÿ™ƒ๐Ÿ˜‰๐Ÿ˜Œ๐Ÿ˜๐Ÿฅฐ๐Ÿ˜˜๐Ÿ˜—๐Ÿ˜™๐Ÿ˜š๐Ÿ˜‹๐Ÿ˜›๐Ÿ˜๐Ÿ˜œ๐Ÿคช๐Ÿคจ๐Ÿง๐Ÿค“๐Ÿ˜Ž๐Ÿคฉ๐Ÿฅณ๐Ÿ˜๐Ÿ˜’๐Ÿ˜ž๐Ÿ˜”๐Ÿ˜Ÿ๐Ÿ˜•๐Ÿ™๐Ÿ˜ฃ๐Ÿ˜–๐Ÿ˜ซ๐Ÿ˜ฉ๐Ÿฅบ๐Ÿ˜ข๐Ÿ˜ญ๐Ÿ˜ค๐Ÿ˜ ๐Ÿ˜ก๐Ÿคฌ๐Ÿคฏ๐Ÿ˜ณ๐Ÿฅต๐Ÿฅถ๐Ÿ˜ฑ๐Ÿ˜จ๐Ÿ˜ฐ๐Ÿ˜ฅ๐Ÿ˜“๐Ÿค—๐Ÿค”๐Ÿคญ๐Ÿคซ๐Ÿคฅ๐Ÿ˜ถ๐Ÿ˜๐Ÿ˜‘๐Ÿ˜ฌ๐Ÿ™„๐Ÿ˜ฏ๐Ÿ˜ฆ๐Ÿ˜ง๐Ÿ˜ฎ๐Ÿ˜ฒ๐Ÿ˜ด๐Ÿคค๐Ÿ˜ช๐Ÿ˜ต๐Ÿค๐Ÿฅด๐Ÿคข๐Ÿคฎ๐Ÿคง๐Ÿ˜ท๐Ÿค’๐Ÿค•๐Ÿค‘๐Ÿค ๐Ÿ˜ˆ๐Ÿ‘ฟ๐Ÿ‘น๐Ÿ‘บ๐Ÿคก๐Ÿ‘ฝ๐Ÿค–๐ŸŽƒ';
    const faceArray = [];
    for(let index = 0; index < faces.length; index += 2) {
        let face = faces[index] + faces[index+1]
        faceArray.push(face)
    }
    return faceArray
})();
Enter fullscreen mode Exit fullscreen mode

Now granted, I could have saved that IIFE as its own function and then called it, but I don't need to call it more than ones so why make a reference to a function I only need to ever use once?

Collapse
 
somedood profile image
Basti Ortiz

This is definitely a valid use case for it. I guess the only problem you'll ever face with this approach is readability. At first glance, it isn't really a design pattern you see often. Moreover, the algorithm isn't as straightforward as one may think. Perhaps sprinkling a few comments here and there explaining your rationale and thought processes will help. Other than that, I see nothing wrong with this. ๐Ÿ™‚

Thread Thread
 
sebvercammen profile image
Sรฉbastien Vercammen • Edited

That's because it's an anti-pattern. This is not a valid use case.

  1. An interpreter already executes code, so you don't need to wrap it in a function. Remove the function, and you've got the same output.
  2. If you need to repeat execution, you should define a function with a descriptive name.
  3. In this case, the result will always be the same, and the whole function can be replaced with its output to save on execution time.

An IIFE is still common. It's often used in browser
content extensions or snippets loaded from providers (analytics, ads, ...), because the browser's scope is shared by all active snippets, so we limit the scope.

Thread Thread
 
somedood profile image
Basti Ortiz

Well, there's that, too. ๐Ÿ˜…

Collapse
 
gmartigny profile image
Guillaume Martigny

Hey, I just want to point out that you can do the same with:

const faces = "๐Ÿ˜€๐Ÿ˜ƒ๐Ÿ˜„".match(/.{2}/g);
Enter fullscreen mode Exit fullscreen mode

๐Ÿ˜Ž

Collapse
 
hugo__df profile image
Hugo Di Francesco

The one place where IIFEs can still be useful is surprisingly async/await in Node.

You can't use await outside of async so you end up doing something like:

(async () => {
  try {
    await something()
  } catch(e) {
    console.error(e.stack)
    process.exit(1)
  }
})()

That's unless you have top-level await support in your environment (which Node doesn't but the esm package can be configured to provide or if you transpile)

Collapse
 
citguy profile image
Ryan Johnson

You could argue that the following syntax is subjectively easier to grok, because it's explicit about the definition and execution of the async function main. However, there's no technical advantage of one syntax vs the other.

async function main () {
  try {
    await something()
  } catch (e) {
    console.error(e.stack)
    process.exit(1)
  }
}

main()
Collapse
 
somedood profile image
Basti Ortiz

Oh, yeah. How could I forget about that?

Also, just a side note, I just realized now that top-level await is essentially a glorified way of writing top-level synchronous code. That's kind of strange... ๐Ÿค”

Collapse
 
gmartigny profile image
Guillaume Martigny • Edited

Honestly, you should never use a function before its declaration (hoisting or not).

The first is indeed the "default" one. It just put a new function into the scope.

For the second, I see two tiny benefit:

  • You can assign a long and descriptive function name to a shorter variable name. That way, your debugger with put the long function name in stack trace and your code can play with a simpler version.
// Don't go too short neither
const fetch = function fetchAllUsersFromDataBase () { /* ... */ }
const filter = function keepOnlyActiveUsers () { /* ... */ }
return Promise.all([fetch, filter]);
  • You can use the non-scoping property or arrow function. Using this inside an arrow function refer to the wrapping scope.
this.prop = 0;
const action = () => { this.prop++ };
for (let i = 0; i < 5; ++i) action();

Finally, IIFE don't declare a new function. That's indeed just a way to do "private" scoping.

const measure = (function () {
  const cache = {}
  return function measureTextLength (text) {
    if (cache[text]) return cache[text];
    const result = timeConsumingFunction();
    cache[text] = result;
    return result;
  }
})();
Collapse
 
citguy profile image
Ryan Johnson

Personally, I favor function declarations. However, if the logic is simple enough, I'll consider using a function expression using an arrow function (typically for functional composition).

Both of these are functionally equivalent:

const isTruthy = (item) => item

function isTruthyLong (item) {
  return item
}

When you use them as the callback for array.filter() they behave the same.

let a = Array(3) // [undefined, undefined, undefined]
a[1] = 'Hi mom!'
let short = a.filter(isTruthy)
let long = a.filter(isTruthyLong)
console.log(a) // [undefined, 'Hi mom!', undefined]
console.log(short) // ['Hi mom!']
console.log(long) // ['Hi mom!']

CodePen Demo

Collapse
 
adam_cyclones profile image
Adam Crockett ๐ŸŒ€

I used an IIFE recently to make computed properties on an object literal.

Given the following code:

attachEvent({
click: "some-element",
});

Now imagine I want that 'some-element' to be conditional. In this circumstance for the framework I'm using I cannot do this in a traditional way, that method is part of a class.

So I do this.

attachEvent((() => {
cont day = this.day; // value is wed
return {
click: day === 'thurs' ? 'some-element' : 'another-element';
}
})());

The result is a button that only works on Thursdays. The object was built up and returned in place by the IIFE. It was an edge case but it's certainly one way of doing it.

Very terse code though and maybe not great for your team.