
Immediately invoked functions
An immediately invoked function expression (IIFE) is a design pattern that produces a lexical scope using function scoping. An IIFE can be used to avoid variable hoisting from within blocks, or to prevent us from polluting the global scope—for example:
let bar = 0; // global (function() { let foo: number = 0; // In scope of this function bar = 1; // Access global scope console.log(bar); // 1 console.log(foo); // 0 })(); console.log(bar); // 1 console.log(foo); // Error
In the preceding example, we have wrapped the declaration of a variable (foo) with an IIFE. The foo variable is scoped to the IIFE function and is not available in the global scope, which explains the error that is thrown when we try to access it on the last line.
The bar variable is a global. Therefore, it can be accessed from both the inside and the outside of the IIFE function.
We can also pass a variable to the IIFE to have better control over the creation of variables outside its scope:
let bar = 0; // global let topScope = window; (function(global: any) { let foo: number = 0; // In scope of this function console.log(global.bar); // 0 global.bar = 1; // Access global scope console.log(global.bar); // 1 console.log(foo); // 0 })(topScope); console.log(bar); // 1 console.log(foo); // Error
Furthermore, IIFE can help us to simultaneously allow public access to methods while retaining privacy for variables defined within the function. Let's look at an example:
class Counter { private _i: number; public constructor() { this._i = 0; } public get(): number { return this._i; } public set(val: number): void { this._i = val; } public increment(): void { this._i++; } } let counter = new Counter(); console.log(counter.get()); // 0 counter.set(2); console.log(counter.get()); // 2 counter.increment(); console.log(counter.get()); // 3 console.log(counter._i); // Error: Property '_i' is private
We have defined a class named Counter, which has a private numeric attribute named _i. The class also has methods to get and set the value of the private _i property.
We have also created an instance of the Counter class and invoked the set, get, and increment methods to observe that everything is working as expected. If we attempt to access the _i property in an instance of Counter, we will get an error because the variable is private.
If we compile the preceding TypeScript code (only the class definition) and examine the generated JavaScript code, we will see the following:
var Counter = (function() { function Counter() { this._i = 0; } Counter.prototype.get = function() { return this._i; }; Counter.prototype.set = function(val) { this._i = val; }; Counter.prototype.increment = function() { this._i++; }; return Counter; })();
This generated JavaScript code will work perfectly in most scenarios, but if we execute it in a browser and try to create an instance of Counter and access its _i property, we will not get any errors because TypeScript will not generate runtime private properties for us. Sometimes we will need to write our classes in such a way that some properties are private at runtime—for example, if we release a library that will be used by JavaScript developers.
We can also use IIFE to simultaneously allow public access to methods while retaining privacy for variables defined within the function:
var Counter = (function() { var _i: number = 0; function Counter() { // } Counter.prototype.get = function() { return _i; }; Counter.prototype.set = function(val: number) { _i = val; }; Counter.prototype.increment = function() { _i++; }; return Counter; })();
In the preceding example, everything is almost identical to the TypeScript's generated JavaScript, except now the variable _i is an object in the Counter closure instead of a property of the Counter class.
If we run the generated JavaScript output in a browser and try to invoke the _i property directly, we will notice that the property is now private at runtime:
let counter = new Counter(); console.log(counter.get()); // 0 counter.set(2); console.log(counter.get()); // 2 counter.increment(); console.log(counter.get()); // 3 console.log(counter._i); // undefined