TypeScript入門 - 4

by vivid 31. 十月 2018 10:55

.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號: N181020003
出刊日期: 2018/10/31

在ECMAScript 5版之前,JavaScript使用constructor(建構函式)與原型繼承(Prototype inheritance)來達到重複使用程式碼的動作,但其語法與架構跟真正物件導向程式語言相比而言,仍有很大的差異。從ECMAScript 6 版(正式名稱為ECMAScript 2015)之後,JavaScript引進了類別(class)語法,讓我們更容易用新語法來建立物件導向的程式。而TypeScript則增強類別的語法,透過型別檢查的機制讓我們能夠在程式設計階段便排除語法上的錯誤,並提供成員存取修飾詞以控制類別成員可視性。本篇文章將介紹類別的語法。

 

constructor(建構函式)

在TypeScript中,使用「class」關鍵字來建立類別,「class」關鍵字後接類別的名稱。TypeScript中的類別(Class)預設都有建構函式,若沒有在類別中定義建構函式,則編譯器會自動產生預設建構函式。預設類別的語法以「class」關鍵字開始,接續類別名稱,類似ECMAScript 2015、C#、Java語法,參考以下範例程式碼定義一個「Employee」類別:

class Employee {

}

使用tsc工具程式將其轉換成JavaScript,參考程式碼如下,預設類別會轉換成函式:

var Employee = /** @class */ (function () {
    function Employee() {
    }
    return Employee;
}());

我們來看一個稍微複雜一點的例子,參考以下範例程式碼,「Employee」類別中包含「empId」、「empName」兩個屬性;一個建構函式,以及一個「getInfo」方法:

class Employee {
  empId: number;
  empName: string;
  constructor( id: number, name: string ) {
    this.empId = id;
    this.empName = name;
  }
  getInfo() {
    return ` ${this.empId} , ${this.empName} `;
  }
}

let emp1 = new Employee( 1, "mary" );
console.log( emp1.getInfo() ); // 1 , mary

 

在TypeScript之中,通常實體屬性(Instance Property)會宣告在建構函式(Constructor)之前。屬性的語法包含三部分:第一為選擇性的成員存取修飾詞;第二部分為屬性名稱,第三部分為屬性型別。

方法定義時不需要撰寫「function」關鍵字,方法前方也可以使用成員存取修飾詞來設定可視性。在「getInfo」方法之中,使用「this」來存取「Employee」類別的empId」、「empName」成員。類別定義完成之後,我們便可以使用「new」關鍵字建立emp1物件,建立物件時會自動叫用建構函式(constructor)進行初始化,最後我們叫用「getInfo」方法取得員工資料。。

使用tsc工具程式將其轉換成JavaScript,參考程式碼如下:

var Employee = /** @class */ (function () {
    function Employee( id, name ) {
        this.empId = id;
        this.empName = name;
    }
    Employee.prototype.getInfo = function () {
        return " " + this.empId + " , " + this.empName;
    };
    return Employee;
}());
var emp1 = new Employee( 1, "mary" );
console.log( emp1.getInfo() ); // 1 , mary

若在類別中宣告屬性時順帶指定初始值,例如以下範例程式碼:

class Employee {
  empId: number = 999;
  empName: string = 'Candy';
  constructor( id: number, name: string ) {
    this.empId = id;
    this.empName = name;
  }
  getInfo() {
    return ` ${this.empId} , ${this.empName} `;
  }
}

let emp1 = new Employee( 1, "mary" );
console.log( emp1.getInfo() ); // 1 , mary

 

則設定屬性初始值的程式碼,會移到建構函式之中,例如將上述程式碼,使用tsc工具將其轉換成JavaScript,參考程式碼如下:

var Employee = /** @class */ (function () {
    function Employee(id, name) {
        this.empId = 999;
        this.empName = 'Candy';
        this.empId = id;
        this.empName = name;
    }
    Employee.prototype.getInfo = function () {
        return " " + this.empId + " , " + this.empName;
    };
    return Employee;
}());
var emp1 = new Employee(1, "mary");
console.log( emp1.getInfo() ); // 1 , mary

 

使用成員存取修飾詞

成員存取修飾詞(Access Modifier)可以用來設定類別中成員(包含屬性與方法)的可視性,包含「public」、「protected」與「private」。預設屬性與方法都是「public」,不受限外部程式碼存取,你不需要在成員的前方前置「public」關鍵字。若宣告為「private」則代表只有類別中的程式才可以存取這個成員,類別外部程式碼,不可存取此成員,若外部程式碼存取到「private」成員,則編譯程式碼時將會失敗。「protected」則應用在有繼承關係的類別程式碼可存取,若子類別要存取到父類別的成員,父類別的成員必需宣告為「public」或「protected」,不可以宣告為「private」。

在建構函式宣告參數時,若參數名稱前方加上成員存取修飾詞,如「public」、「private」關鍵字,那麼TypeScript會自動將參數對應到類別成員,參數將會自動變成屬性,參考以下程式碼:

class Employee {
  constructor( public empId: number, public empName: string ) {
  }
  getInfo() {
    return ` ${this.empId} , ${this.empName} `;
  }
}

let emp1 = new Employee( 1, "mary" );
console.log( emp1.getInfo() ); // 1 , mary
console.log( emp1.empId ) // 1
console.log( emp1.empName ) // mary


使用「tsc」工具將其轉換成JavaScript,參考程式碼如下,「empId」、「empName」參數將會在建構函式中自動定義成「empId」、「empName」屬性:

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;
}());
var emp1 = new Employee(1, "mary");
console.log( emp1.getInfo() ); // 1 , mary
console.log( emp1.empId ); // 1
console.log( emp1.empName ); // mary

 

若使用「private」關鍵字宣告建構函式參數「empId」與「empName」,參考以下範例程式碼:

class Employee {
  constructor( private empId: number, private empName: string ) {
  }
  getInfo() {
    return ` ${this.empId} , ${this.empName} `;
  }
}

let emp1 = new Employee( 1, "mary" );
console.log( emp1.getInfo() ); // 1 , mary


 

再使用「tsc」工具程式將其轉換成JavaScript程式碼如下,結果和使用「public」關鍵字產生出的JavaScript程式碼完全相同:

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;
}());
var emp1 = new Employee(1, "mary");
console.log( emp1.getInfo() ); // 1 , mary

 

兩者最大的差別是在編譯階段,使用「private」關鍵字宣告參數時,若外部程式碼直接存取到「private」成員,則編譯程式碼時會失敗。而在執行階段,不管使用「public」或「private」關鍵字,都不會限定成員的可視性。

class Employee {
  constructor( private empId: number, private empName: string ) {
  }
  getInfo() {
    return ` ${this.empId} , ${this.empName} `;
  }
}

let emp1 = new Employee( 1, "mary" );
console.log( emp1.getInfo() ); // 1 , mary
console.log( emp1.empId ); // error
console.log( emp1.empName ); // error

 

我們也可以直接在類別中將類別屬性宣告為「private」,來達成編譯階段檢查程式正確性的效果,只是這樣會浪費一些時間敲打多餘的程式碼,參考以下範例程式碼:

class Employee {
  private empId: number;
  private empName: string;
  constructor( id: number, name: string ) {
    this.empId = id;
    this.empName = name;
  }
  getInfo() {
    return ` ${this.empId} , ${this.empName} `;
  }
}

let emp1 = new Employee( 1, "mary" );
console.log( emp1.getInfo() ); // 1 , mary

console.log( emp1.empId ); // error
console.log( emp1.empName ); // error


 

 

getter 與setter存取子

類別(Class)之中可以宣告「getter」與「setter」存取子,來存取屬性值,它們實際上是方法。若要定義「getter」存取子讀取屬性值,只要使用「get」關鍵字,後面接屬性名稱,與小括號()。若要定義「setter」存取子修改屬性值,只要使用「set」關鍵字,後面接屬性名稱,與小括號(),小括號之中傳入值。

參考以下範例程式碼,定義一個名為「employeeName」的「getter」存取子來讀取員工名稱;一個「setter」存取子來修改員工名稱:

class Employee {
  constructor( private empId : number, private empName: string ) {
  }
  getInfo() {
    return ` ${this.empId} , ${this.empName} `;
  }
  get employeeName() {
    return this.empName;
  }
  set employeeName( value : string ) {
    this.empName  = value;
  }
}

let emp1 = new Employee( 1, "mary" );
console.log( emp1.getInfo() ); // 1 , mary
emp1.employeeName = "candy";
console.log( emp1.employeeName ) //Candy

 

使用「tsc」工具程式將其轉換成JavaScript程式碼參考如下:

class Employee {
    constructor( empId, empName ) {
        this.empId = empId;
        this.empName = empName;
    }
    getInfo() {
        return ` ${this.empId} , ${this.empName} `;
    }
    get employeeName() {
        return this.empName;
    }
    set employeeName( value ) {
        this.empName = value;
    }
}
let emp1 = new Employee( 1, "mary" );
console.log( emp1.getInfo() ); // 1 , mary
emp1.employeeName = "candy";
console.log( emp1.employeeName ); //Candy

 

因為不是所有版本的JavaScript都有支援「getter」與「setter」存取子,因此若使用「tsc」工具程式編譯TypeScript可能會發生以下錯誤:

TS1056: Accessors are only available when targeting ECMAScript 5 and higher.

解法方式只要在編譯程式時,設定「target」參數,指定要輸出的ECMAScript版本便可以解決這個問題,舉例來說,設定目標版本為ECMAScript 2016時,其指令:

 

tsc --target ES2016 TypeScript檔案名稱

 

靜態屬性(Static Property)

TypeScript新增「static」關鍵字,讓類別可以定義靜態成員,例如靜態屬性(Static Property)或靜態方法(Static Method)。通常靜態屬性(Static Property)都是使用在定義物件共用的資料,靜態屬性要呼叫時直接使用「類別名稱.屬性名稱」語法,不需要先建立物件實體。

參考以下範例程式碼,在「Employee」類別的宣告之中,定義一個「companyName」靜態屬性,屬性前方要加上「static」關鍵字:

class Employee {
  static companyName : string ="XCom";
  constructor( private empId: number, private empName: string ) {
  }
  getInfo() {
    return ` ${this.empId} , ${this.empName} `;
  }
}

let emp1 = new Employee( 1, "mary" );
console.log( emp1.getInfo() ); // 1 , mary
console.log( Employee.companyName ) // XCom

 

使用「tsc」工具程式將其轉換成JavaScript程式碼參考如下,靜態屬性會關聯到建構函式(constructor),而不是物件的「Prototype」:

var Employee = /** @class */ (function () {
    function Employee( empId, empName ) {
        this.empId = empId;
        this.empName = empName;
    }
    Employee.prototype.getInfo = function () {
        return " " + this.empId + " , " + this.empName;
    };
    Employee.companyName = "XCom";
    return Employee;
}());
var emp1 = new Employee( 1, "mary" );
console.log( emp1.getInfo() ); // 1 , mary
console.log( Employee.companyName ); // XCom

 

靜態方法(Static Method)

在類別中方法前方加註「static」關鍵字可以定義靜態方法(Static Method),靜態方法(Static Method)要呼叫時直接使用「類別名稱.方法名稱」語法,不需要先建立物件實體,通常靜態方法都是使用在公用程式,靜態方法會關聯到建構函式(constructor),而不是物件的「Prototype」。。

參考以下範例程式碼,在「Employee」類別的宣告之中,定義一個「getCompanyName」靜態方法,方法前方要加「static」關鍵字,靜態方法不可存取到實體成員(含屬性和方法),例如不能夠使用「this.empId」來存取「Employee」類別的屬性:

class Employee {
  static companyName: string = "XCom";
  constructor( private empId: number, private empName: string ) {
  }
  getInfo() {
    return ` ${this.empId} , ${this.empName} `;
  }
  static getCompanyName() { return Employee.companyName; }
}

let emp1 = new Employee(1, "mary");
console.log(emp1.getInfo()); // 1 , mary
console.log(Employee.companyName) // XCom
console.log(Employee.getCompanyName()) // XCom

 

使用「tsc」工具程式將其轉換成JavaScript結果參考如下,靜態方法(Static Method)會關聯到建構函式(constructor),而不是物件的「Prototype」:

var Employee = /** @class */ (function () {
    function Employee( empId, empName ) {
        this.empId = empId;
        this.empName = empName;
    }
    Employee.prototype.getInfo = function () {
        return " " + this.empId + " , " + this.empName;
    };
    Employee.getCompanyName = function () { return Employee.companyName; };
    Employee.companyName = "XCom";
    return Employee;
}());
var emp1 = new Employee( 1, "mary" );
console.log( emp1.getInfo() ); // 1 , mary
console.log( Employee.companyName ); // XCom
console.log( Employee.getCompanyName() ); // XCom

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