DEV Community ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป

Oinak
Oinak

Posted on

understanding IIFE's step by step

Immediately invoked function expression

Wow, that's a mouthful. According to wikpedia, an IFFE is ยซa JavaScript programming language idiom which produces a lexical scope using JavaScript's function scoping.ยป

But let's assume lexical scope, variable hoisting and function scoping are not terms you are confortable with yet, don't worry, let's see what an IFFE actually does, with examples, one bit at a time.

Basically and IIFE is:

  • a function
  • evaluated immediately
  • returning an object
  • with public attributes (methods and values)
  • that can refer to non public ones
  • and not exposing the private ones

So the simplest form is this:

f1 = function(){
  let secret_a = 1;
  function secret_b(){
    return 2;
  }

  return { public_a: secret_a, public_b: secret_b };
}

let obj1 = f1()

console.log('obj1.public_a: ' + obj1.public_a);         // obj1.public_a: 1
console.log('obj1.public_b: ' + obj1.public_b());       // obj1.public_b() 2
console.log('obj1.secret_a: ' + typeof(obj1.secret_a)); // obj1.secret_a: undefined
console.log('obj1.secret_b: ' + typeof(obj1.secret_b)); // obj1.secret_b: undefined
Enter fullscreen mode Exit fullscreen mode

Now imagine we replace f1 with everything that was on the right of f1 =

let obj2 = (
  // here starts f1
  function(){
    let secret_a = 1;
    function secret_b(){
      return 2;
    }

    return { public_a: secret_a, public_b: secret_b };
  } 
  // here ends f1
)()

console.log('obj2.public_a: ' + obj2.public_a);         // obj2.public_a: 1
console.log('obj2.public_b: ' + obj2.public_b());       // obj2.public_b() 2
console.log('obj2.secret_a: ' + typeof(obj2.secret_a)); // obj2.secret_a: undefined
console.log('obj2.secret_b: ' + typeof(obj2.secret_b)); // obj2.secret_b: undefined
Enter fullscreen mode Exit fullscreen mode

This has the same effect, and we already recognise the IIFE famous form.

(function(){ ... })()
Enter fullscreen mode Exit fullscreen mode

But an IIFE can not only return a new object, it can add stuff to one:

let obj3 = { prop1: 3 };

let obj4 = (function(expose){ // we call expose what comes from outside
  function secret_b(){
    return 2;
  }

  expose.public_b = function(){ // we add properties to expose
    return secret_b() + expose.prop1; // and read from it
  }

  return expose; // we return the received object with extra stuff
})(obj3); // we call the IIFE with some object

console.log('obj4.prop1: ' + obj4.prop1);         // obj4.prop1: 3
console.log('obj4.public_b: ' + obj4.public_b()); // obj4.public_b() 5
Enter fullscreen mode Exit fullscreen mode

Observe there were four changes here:

  • let obj4 = (function(expose){ we name the argument we expect
  • expose.public_b = function(){ we add stuff to the received object
  • return expose; we return the anriched object
  • })(obj3); we call the IIFE with an argument from outside

But nowadays, everyone is loading multiple files having complex pipelines, and here IIFES can help you by being able to enrich them selves:

// file 1
MyObj = (function(expose){
  let secret_b = 4;

  expose.public_b = function(){
    return secret_b;
  }
  return expose;
})(window.MyObj || {});

// file 2
MyObj = (function(expose){
  expose.public_c = function(){
    return expose.public_b() + 5;
  }
  return expose;
})(window.MyObj || {});

console.log('myObj.secret_b: ' + typeof(MyObj.secret_b)); // myObj.secret_b(): undefined
console.log('myObj.public_b: ' + MyObj.public_c());       // myObj.public_b() 9

Enter fullscreen mode Exit fullscreen mode

This works for any order file 1 and file 2 get loaded, so you can have some basic/shared stuff in your object and augment it as needed on certain pages but only if an when the user loads them.

This can get pretty crazy soon, so it's better to impose some conventions on it, so you know what to expect and where to put things:

// Use you company or app name here to keep or your stuff namespaced
// separatedly from 3rd party libraries
window.Namespace = window.Namespace || {};

// Componen documentation
// What it does: functional description
// Example usage:
//   Namespace.Component.init({ selector: '#some_id', some_param: 30 });
window.Namespace.Component = (function(expose){
  let private_some;

  function private_helper(){
    // stuff
  }

  expose.public_method = function(){
    // stuff
  }

  expose.init = function(options){
    private_some = option.some;
  }

  return expose;
})(window.Namespace.Component || {});
Enter fullscreen mode Exit fullscreen mode

Then you can use:

Namespace.Component.init({
  selector: '#some_id',
  some_param: 30
});
Enter fullscreen mode Exit fullscreen mode

On your html files so you have the selector definition and reference on the same place, easy to modify if the html needs to change.

I would additionally recommend to always use 'js-xxxx'-style classes and ids (or data-attributes) so that they do not interfere with design/layout tasks.

So, what do you think? do you use a similar pattern already? did this help you wrap your head around IIFE's. Is there anything here which purpose is still not clear to you.

Top comments (0)

๐ŸŒš Life is too short to browse without dark mode