Learning TypeScript 2.x
上QQ阅读APP看书,第一时间看更新

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.

By convention, TypeScript and JavaScript developers usually name private variables with names preceded by an underscore ( _).

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.

Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure remembers the environment (variables in the scope) in which it was created. We will discover more about closures in Chapter 6, Understanding the Runtime.

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 
In some cases, we will need to have precise control over scope and closures, and our code will end up looking much more like JavaScript. As long as we write our application components (classes, modules, and so on) to be consumed by other TypeScript components, we will rarely have to worry about implementing runtime private properties. We will look in depth at the TypeScript runtime in Chapter 6, Understanding the Runtime.