TypeScript入門 - 5

by Vivid 28. 十一月 2018 11:29

.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
});

Tags:

.NET Magazine國際中文電子雜誌 | TypeScript | 許薰尹Vivid Hsu

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List