I. namespace - namespace

If you are familiar with C++, Java, C#, etc., namespace should not be new to you. namespace can be used to encapsulate a piece of code, and code outside namespace cannot directly access the code inside namespace.

Namespaces are defined by the namespace keyword. The format is as follows.

1
2
3
namespace namespace_name {
    // 命名空间内部代码
}

In the following example, _name and getName inside Lib are not accessible outside the Lib namespace.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// index.ts
namespace Lib {
    const _name = '小明';

    function getName() {
        return _name;
    }
}

console.log(_name); // Error: Cannot find name '_name'
console.log(getName()); // Error: Cannot find name 'getName'

If you use tsc to compile the above code, the compiler will report an error directly.

Since JavaScript does not support namespace syntax, how does typescript implement namespaces? To understand how it works, first comment out the last two lines of code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// index.ts
namespace Lib {
    const _name = '小明';

    function getName() {
        return _name;
    }
}

// console.log(_name); 
// console.log(getName());
1
tsc index.ts

The contents of the compiled js file are as follows.

1
2
3
4
5
6
7
var Lib;
(function (Lib) {
    var _name = '小明';
    function getName() {
        return _name;
    }
})(Lib || (Lib = {}));

As you can see, the namespace principle is implemented through Immediately Execute Function (IIFE), where the function is executed and the variables inside the function are not available from the outside world (global scope).

In order to get the variables or functions inside namespace, you can expose the variables in namespace through the export keyword and then access the exposed variables through namespace names .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
namespace Lib {
    const _name = '小明';

    // 使用export关键字导出getName
    export function getName() {
        return _name;
    }
}

// 通过命名空间名称访问内部的变量(函数)
console.log(Lib.getName());

Compiled with tsc, the compilation passed and the compiled js file reads as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var Lib;
(function (Lib) {
    var _name = '小明';
    // 使用export关键字导出getName
    function getName() {
        return _name;
    }
    Lib.getName = getName;
})(Lib || (Lib = {}));
// 通过命名空间名称访问内部的变量(函数)
console.log(Lib.getName());

You can see that the compiled code, by assigning the getName function to Lib.getName, implements the export function, so you can access the variables inside the namespace from outside the namespace.

As you can see from the compiled js code, namespace is essentially an object, and we can access the variables inside the namespace through the properties of the object .

II. Exporting types and namespaces

As with modules, you can export type information from namespaces and access the exported types by the namespace name.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
namespace Home {
    export interface Person {
        name: string;
        age: number;
    }

    export const child: Person = {
        name: "小明",
        age: 6
    };
}

const man: Home.Person = {
    name: "xx",
    age: 20
};

The compiled js code is as follows. The compiled js file does not contain any type information.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var Home;
(function (Home) {
    Home.child = {
        name: "小明",
        age: 6
    };
})(Home || (Home = {}));
var man = {
    name: "xx",
    age: 20
};

Namespaces can be nested and child namespaces can be exported by the parent namespace and then access the variables of the internal namespace through the namespace name chain.

1
2
3
4
5
6
7
namespace Outer {
    export namespace Inner {
        export const a = 3;
    }
}

console.log(Outer.Inner.a);

The compiled js file is as follows.

1
2
3
4
5
6
7
8
9
var Outer;
(function (Outer) {
    var Inner;
    (function (Inner) {
        Inner.a = 3;
    })(Inner = Outer.Inner || (Outer.Inner = {}));
})(Outer || (Outer = {}));

console.log(Outer.Inner.a);

III. Aliases

Because namespaces can be nested, when the embedding level is very deep, it is troublesome to access through namespace name chain, for example Space1.Space2.Space3.Space4.xxx, you can simplify the namespace name chain by aliasing.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace MyLibA {
    export namespace Types {
        export interface Person {
            name: string;
            age: number;
        }
    }
    export namespace Functions {
        export function getPerson(name: string, age: number):
            Types.Person {
            return {name, age};
        }
    }
}

// 通过别名简化命名空间名称链
var API_FUNCTIONS = MyLibA.Functions;
const ross = API_FUNCTIONS.getPerson('Ross Geller', 30);

// Error: Property 'Types' does not exist on type 'typeof MyLibA'
// 因为Types命名空间仅包含类型信息,编译后的js代码,类型信息会被移除
// var API_TYPES = MyLibA.Types;

The above code simplifies access to MyLibA.Functions by adding an alias to var API_FUNCTIONS = MyLibA.Functions;.

But using the same approach, adding an alias to MyLibA.Types will report an error because the MyLibA.Types namespace only contains type information internally, no other fields exist, so it is essentially non-existent (the compiled JS code will remove the type information). You can use type Person = MyLibA.Types.Person to simplify access.

TypeScirpt also supports using the import <alias> = statement to simplify access to internal namespaces, and adding aliases to MyLib.Types does not report an error, which is a syntactic sugar given to us by typescript to create aliases for namespaces.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
namespace MyLibA {
    export namespace Types {
        export interface Person {
            name: string;
            age: number;
        }
    }
    export namespace Functions {
        export function getPerson(name: string, age: number):
            Types.Person {
            return {name, age};
        }
    }
}

// 通过别名简化命名空间名称链
import API_FUNCTIONS = MyLibA.Functions;
import API_Types = MyLibA.Types; // 使用'import <alias> ='语句,不会报错

const ross: API_Types.Person = API_FUNCTIONS.getPerson('Ross Geller', 30);

IV. Importing Namespaces

Since a namespace is essentially an Object, you can import namespaces with the import statement.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ---a.ts

// 导出命名空间Home
export namespace Home {
    export interface Person {
        name: string;
        age: number;
    }

    export const child: Person = {
        name: "小明",
        age: 6
    };
}

--------------------------------------------------
// ---b.ts

// 导入命名空间Home
import {Home} from "./a";

console.log(Home.child.name);

To import namespaces, you need the execution environment of the code to support namespaces, the above example is ES Modules, if it is a NodeJS environment, which supports the CommonJS module system, then you need to use require, exports statements to import and export.

V. Modularity

Typescript provides //, which works only during the ts compilation phase and is used to instruct the ts compiler to locate ts files.

1
/// <reference path="./b.ts" />

/// <reference path="" /> is similar to #include in the c language. It must appear at the top of the file and is essentially a comment, so it is only useful at the compile stage.

The value of the path attribute specified by reference is the path to another ts file, which is used to tell the compiler what dependencies the current file compiles with, somewhat similar to the import statement, but without the need to import the specified variables.

When reference specifies a file, typescript will automatically include this file in the compilation process when compiling, and all global variables within this file will be available in the current file (the file specified by reference to exist).

As an example, in index.ts, all global variables are obtained in the current file (the file specified by reference) via /// <reference path=". /math.ts" /> introduces the math.ts file.

1
2
3
4
5
6
// ---math.ts
namespace MyMath {
    export const add = (a: number, b: number) => {
        return a + b;
    }
}
1
2
3
4
// ---index.ts
/// <reference path="./math.ts" />

MyMath.add(3, 4);

Compiled by tsc index.ts, after compilation there are two files index.js and math.js with the following contents.

1
2
3
// ---index.js
/// <reference path="./math.ts" />
MyMath.add(3, 4);
1
2
3
4
5
6
7
// ---math.js
var MyMath;
(function (MyMath) {
    MyMath.add = function (a, b) {
        return a + b;
    };
})(MyMath || (MyMath = {}));

Of course we can’t execute this code in the Node environment because it’s two separate files and there’s no require statement. We need to first package them into a single file bundle.js and then execute it with the command node boundle.js.

In the browser environment, we need to load the math.js and index.js files sequentially using the <script> statement.

1
2
<script src="./math.js"></script>
<script src="./index.js"></script>

A better approach is to use the -outFile configuration option of tsc to package the output file into a bundle, and ts will automatically compile the file according to the reference command.

Use the tsc --outFile bundle.js index.ts command to compile the file, and the compiled bundle.js file will have the following contents.

1
2
3
4
5
6
7
8
var MyMath;
(function (MyMath) {
    MyMath.add = function (a, b) {
        return a + b;
    };
})(MyMath || (MyMath = {}));
/// <reference path="./math.ts" />
MyMath.add(3, 4);

VI. Extending namespaces

You can extend an already defined namespace using the reference directive. Look directly at the following example.

1
2
3
4
5
6
// ---a.ts
/// <reference path="./b.ts"/>
const john: MyLibA.Person = MyLibA.defaultPerson;
const ross: MyLibA.Person = MyLibA.getPerson( 'Ross Geller', 30 );
console.log( john ); // {name: 'John Doe', age: 21}
console.log( ross ); // {name: 'Ross Geller', age: 30}
1
2
3
4
5
// ---b.ts
/// <reference path="./c.ts" />
namespace MyLibA {
    export const defaultPerson: Person = getPerson( 'John Doe', 21 );
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// c.ts
namespace MyLibA {
    export interface Person {
        name: string;
        age: number;
    }
    export function getPerson( name: string, age: number ): Person {
        return { name, age };
    }
}

In the b.ts file, c.ts is introduced via the reference directive, which extends MyLibA, adds the defaultPerson variable, and all variables in MyLibA are accessible in the b.ts file, e.g. getPerson( 'John Doe', 21 );

In the a.ts file, the b.ts is introduced with the reference directive, and the Person, getPerson and defaultPerson members inside the namespace MyLibA are accessible in the a.ts file.

VII. Recommendations

This is the end of this chapter. namespaces are powerful, but if you ask me, when should I use namespaces? I would say, try to avoid namespaces and use the Modules system instead, now that Es Module is convenient, you can also use CommonJS instead of namespaces in node environments.

Namespaces came before ES Module, so maybe one day namespaces will be deprecated.