.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號: N181120102
出刊日期: 2018/11/28
模組(Module)用於檔案管理,並可動態載入到程式之中,包含兩種:內部模組(Internal modules)與外部模組(External modules)。在TypeScript 1.5版本之後變更此兩種模組命名方式,其中內部模組(Internal modules)變更為「namespace(命名空間)」;而外部模組(External modules)直接命名為模組(Module),本篇文章將簡介命名空間,以及如何定義與使用模組。
內部模組(Internal modules),現在稱為命名空間(namespace),會將成員包在一個函式之中,以限定範圍(Scope),成員可能包含變數(Variable)、函式(function)、物件(Object)、類別(Class)與介面(Interface)等等。內部模組的名稱會添加到全域範圍(Global Scope),且匯出的成員可透過全域範圍的模組識別字(globally scoped module identifier)存取。
用來動態將模組檔案載入程式常見的兩種標準:
1. CommonJS:為一套常用於JavaScript的框架類別庫,專注在使用JavaScript開發伺服端的程式碼。
2. AMD (Asynchronous Module Definition):是一個用於定義模組的API,支援非同步模組載入功能,常用於網站應用程式,在瀏覽器用來載入模組。
外部模組(External modules)不會添加任何東西到全域範圍(Global Scope),當使用CommonJS規範載入模組時,外部模組(External modules)成員將透過別名(alias)來存取之,而使用AMD規範載入模組時則是搭配回呼函式,將外部模組限定在回呼函式的範圍。
內部模組(Internal modules)
內部模組(Internal modules)在TypeScript 1.5版本之後,變更名稱為命名空間(Namespace)。用來將相關的功能打包在一起,同時可以避免命名衝突問題。包含變數(Variable)、函式(function)、物件(Object)、類別(Class)與介面(Interface)。內部模組定義時,以「module」關鍵字開始,接下來是模組名稱,程式碼再使用{}符號包起來。在模組中的項目預設只有模組內的程式碼可以存取,若要開放外部存取,需要明確使用「export」關鍵字匯出功能。
參考以下範例程式碼,定義一個「MyModule」模組,在「MyModule」模組之中使用「export」關鍵字匯出一個「Employee」類別,接著便可以使用「模組名稱.項目名稱」語法存取到「Employee」類別:
module MyModule {
export class Employee {
constructor( public empId: number, public empName: string ) {
}
getInfo() {
return ` ${this.empId} , ${this.empName} `;
}
}
}
let e1 = new MyModule.Employee( 1, "mary" );
console.log( e1.empId ); // 1
console.log( e1.empName ); // mary
使用「tsc」工具程式將其轉換成JavaScript,參考程式碼如下,模組將轉換成一個IIFE函式:
var MyModule;
(function ( MyModule ) {
var Employee = /** @class */ (function () {
function Employee( empId, empName ) {
this.empId = empId;
this.empName = empName;
}
Employee.prototype.getInfo = function () {
return " " + this.empId + " , " + this.empName;
};
return Employee;
}());
MyModule.Employee = Employee;
})( MyModule || (MyModule = {}) );
var e1 = new MyModule.Employee( 1, "mary" );
console.log( e1.empId ); // 1
console.log( e1.empName ); // mary
讓我們改用「namespace」關鍵字,改寫上述程式碼,程式如下:
namespace MyModule {
export class Employee {
constructor( public empId: number, public empName: string ) {
}
getInfo() {
return ` ${this.empId} , ${this.empName} `;
}
}
}
let e1 = new MyModule.Employee( 1, "mary" );
console.log( e1.empId ); // 1
console.log( e1.empName ); // mary
若使用「tsc」工具程式將其轉換成JavaScript,你將發現產生出來的程式碼,和上例使用「module」關鍵字定義的程式一模一樣,不再表列出來。
使用「import」匯入模組
在內部模組中,可以使用「import」關鍵字來匯入其它模組,如此便可以使用到其他模組的功能。參考以下範例程式碼,在「MyModule2」模組中,使用「import」關鍵字匯入「MyModule」模組,並為「MyModule.Employee」類爺,指定一個別名「Emp」,如此就可以在「MyModule2」模組程式中使用一個簡短的名字來存取「MyModule.Employee」類別:
module MyModule {
export class Employee {
constructor( public empId: number, public empName: string ) {
}
getInfo() {
return ` ${this.empId} , ${this.empName} `;
}
}
}
module MyModule2 {
import Emp = MyModule.Employee;
export function getEmp() {
return new Emp( 1, "mary" );
}
}
let e1 = MyModule2.getEmp();
console.log( e1.empId ); // 1
console.log( e1.empName ); // mary
使用「tsc」工具程式將其轉換成JavaScript,參考程式碼如下:
var MyModule;
(function ( MyModule ) {
var Employee = /** @class */ (function () {
function Employee( empId, empName ) {
this.empId = empId;
this.empName = empName;
}
Employee.prototype.getInfo = function () {
return " " + this.empId + " , " + this.empName;
};
return Employee;
}());
MyModule.Employee = Employee;
})( MyModule || (MyModule = {}) );
var MyModule2;
(function ( MyModule2 ) {
var Emp = MyModule.Employee;
function getEmp() {
return new Emp( 1, "mary" );
}
MyModule2.getEmp = getEmp;
})( MyModule2 || (MyModule2 = {}) );
var e1 = MyModule2.getEmp();
console.log( e1.empId ); // 1
console.log( e1.empName ); // mary
另一種使用「namespace」關鍵字的寫法如下,只要將「module」關鍵字換成「namespace」即可:
namespace MyModule {
export class Employee {
constructor( public empId: number, public empName: string ) {
}
getInfo() {
return ` ${this.empId} , ${this.empName} `;
}
}
}
namespace MyModule2 {
import Emp = MyModule.Employee;
export function getEmp() {
return new Emp( 1, "mary" );
}
}
let e1 = MyModule2.getEmp();
console.log( e1.empId ); // 1
console.log( e1.empName ); // mary
匯出模組
模組之中可以包含模組,形成巢狀階層式的結構,也可以在模組之中匯出模組。例如以下範例程式碼,「Utils」模組中包含「MyModule」模組:
module Utils {
export module MyModule {
export class Employee {
constructor( public empId: number, public empName: string ) {
}
getInfo() {
return ` ${this.empId} , ${this.empName} `;
}
}
}
}
module MyModule2 {
import Emp = Utils.MyModule.Employee;
export function getEmp() {
return new Emp( 1, "mary" );
}
}
let e1 = MyModule2.getEmp();
console.log( e1.empId ); // 1
console.log( e1.empName ); // mary
使用「tsc」工具程式將其轉換成JavaScript,參考程式碼如下,巢狀式模組產生出巢狀式函式的結構:
var Utils;
(function ( Utils ) {
var MyModule;
(function ( MyModule ) {
var Employee = /** @class */ (function () {
function Employee( empId, empName ) {
this.empId = empId;
this.empName = empName;
}
Employee.prototype.getInfo = function () {
return " " + this.empId + " , " + this.empName;
};
return Employee;
}());
MyModule.Employee = Employee;
})( MyModule = Utils.MyModule || (Utils.MyModule = {}) );
})( Utils || (Utils = {}) );
var MyModule2;
(function ( MyModule2 ) {
var Emp = Utils.MyModule.Employee;
function getEmp() {
return new Emp( 1, "mary" );
}
MyModule2.getEmp = getEmp;
})( MyModule2 || (MyModule2 = {}) );
var e1 = MyModule2.getEmp();
console.log( e1.empId ); // 1
console.log( e1.empName ); // mary
因為巢狀式模組語法較為複雜,我們可以在命名模組時,直接使用「.」符號區隔模組,來達成相同的效果,將上例程式碼修改如下:
module Utils.MyModule {
export class Employee {
constructor( public empId: number, public empName: string ) {
}
getInfo() {
return ` ${this.empId} , ${this.empName} `;
}
}
}
module MyModule2 {
import Emp = Utils.MyModule.Employee;
export function getEmp() {
return new Emp( 1, "mary" );
}
}
let e1 = MyModule2.getEmp();
console.log( e1.empId ); // 1
console.log( e1.empName ); // mary
若使用「tsc」工具程式將其轉換成JavaScript,產生的結果和上例完全相同。
若改用「namespace」關鍵字則寫法如下,當然使用「tsc」工具程式將其轉換成JavaScript,結果也和上例完全相同:
namespace Utils.MyModule {
export class Employee {
constructor( public empId: number, public empName: string ) {
}
getInfo() {
return ` ${this.empId} , ${this.empName} `;
}
}
}
namespace MyModule2 {
import Emp = Utils.MyModule.Employee;
export function getEmp() {
return new Emp( 1, "mary" );
}
}
let e1 = MyModule2.getEmp();
console.log( e1.empId ); // 1
console.log( e1.empName ); // mary
最後要注意的是:內部模組沒有自動載入模組功能,可以自行實做程式碼來載入模組。在網頁中使用它的最簡單方式就是將Typescript程式編譯成一個Javascript檔案,然後再網頁中使用<script>標籤來引用它。
外部模組(External modules)
外部模組(External modules),現在正式名稱命名為「模組(Module)」定義在一個單獨的檔案之中,模組的名稱就是檔案的名稱(去除副檔名)。模組(Module)定義時,程式不需要像內部模組那樣,將程式放在「module」區塊中,模組可以透過模組載入器(Module Loader)來載入執行。
前文提及動態將檔案載入程式可以使用兩種標準:
1. CommonJS: 每次叫用到「require」函式就會載入模組,模組載入動作是同步的,也就是說載入程式的過程中會暫時停止程式的執行,載入完成後才能執行其後的程式碼。適用於NodeJS。
2. AMD (Asynchronous Module Definition):每次叫用到「require」函式就載入模組,模組載入完成時會自動叫用回呼函式,模組未完全載入之前,可以先執行其它程式碼。適用於搭配RequireJS的網站應用程式。
我們來參考一個使用「CommonJS」的範例程式碼,以下程式列表的程式碼將「Employee」模組定義在一個外部檔案「MyModule.ts」之中,同樣利用「export」關鍵字匯出「Employee」類別:
export class Employee {
constructor( public empId: number, public empName: string ) {
}
getInfo() {
return ` ${this.empId} , ${this.empName} `;
}
}
接著在另一個ts檔案(main.ts)之中便可以利用「import」關鍵字搭配「require」函式來匯入外部模組,並使用外部模組中的程式碼,參考以下範例程式碼,「require」函式傳入一個字串代表模組的檔案路徑(去除副檔名):
import MyMod = require('./MyModule')
let e1 = new MyMod.Employee( 1, "mary" );
console.log( e1.empId ); // 1
console.log( e1.empName ); // mary
使用「tsc」工具程式將「main.ts」轉換成JavaScript,參考程式碼如下:
"use strict";
exports.__esModule = true;
var MyMod = require("./MyModule");
var e1 = new MyMod.Employee( 1, "mary" );
console.log( e1.empId ); // 1
console.log( e1.empName ); // mary
將「MyModule.ts」轉換成JavaScript的程式碼如下:
"use strict";
// module MyModule {
// export class Employee {
// constructor( public empId: number, public empName: string ) {
// }
// getInfo() {
// return ` ${this.empId} , ${this.empName} `;
// }
// }
// }
exports.__esModule = true;
var Employee = /** @class */ (function () {
function Employee( empId, empName ) {
this.empId = empId;
this.empName = empName;
}
Employee.prototype.getInfo = function () {
return " " + this.empId + " , " + this.empName;
};
return Employee;
}());
exports.Employee = Employee;
你也可以明確利用「module」參數,指定Typescript編譯器使用CommonJS規範來進行編譯的動作:
tsc --module commonjs main.ts
若要編譯成AMD規範,則可以使用以下指令,搭配「--module amd」參數:
tsc --module amd main.ts
「MyModule.ts」轉換成JavaScript結果如下:
// module MyModule {
// export class Employee {
// constructor( public empId: number, public empName: string ) {
// }
// getInfo() {
// return ` ${this.empId} , ${this.empName} `;
// }
// }
// }
define(["require", "exports"], function ( require, exports ) {
"use strict";
exports.__esModule = true;
var Employee = /** @class */ (function () {
function Employee( empId, empName ) {
this.empId = empId;
this.empName = empName;
}
Employee.prototype.getInfo = function () {
return " " + this.empId + " , " + this.empName;
};
return Employee;
}());
exports.Employee = Employee;
});
使用「tsc」工具程式將「main.ts」轉換成JavaScript結果為:
define( ["require", "exports", "./MyModule"], function ( require, exports, MyMod ) {
"use strict";
exports.__esModule = true;
var e1 = new MyMod.Employee( 1, "mary" );
console.log( e1.empId ); // 1
console.log( e1.empName ); // mary
});