TypeScript手冊翻譯系列3-類

傳統的JavaScript語言基於函數和原型鏈繼承機制的方式構建可重用的組件,但這對於OO編程人員來講顯得比較笨拙,由於是在類的基礎上來繼承。從JavaScript標準ECMAScript 6開始能夠採用面向對象基於類來構建應用。在TypeScript中開發人員如今就可使用這些技術,TypeScript能夠將它們編譯爲目前大多數瀏覽器和平臺能容許的普通Javascript代碼,能夠不用等待下一版本的JavaScript的到來。java

咱們先看一個基於類的簡單例子:
typescript

class Greeter {
    greeting: string;
    constructor(message: string) {        
      this.greeting = message;
    }
    greet() {        
      return "Hello, " + this.greeting;
    }
}

var greeter = new Greeter("world");

這種語法和c#或java語言中的語法很類似。這裏咱們聲明瞭一個'Greeter'類,這個類有三個成員:一個'greeting'屬性,一個構造函數,和一個'greet'方法。
編程

你也許已經注意到了例子中在引用成員時前面的'this.',表示這是一個成員訪問。
c#

在最後一行咱們利用‘new’關鍵字建立了一個'Greeter'類的實例,這會用'Greeter' shape新建一個對象,並調用咱們先前定義的構造函數來初始化此對象。
設計模式

繼承

在TypeScript中咱們可使用咱們經常使用的OO設計模式。固然在基於類的編程中,最基本的一個模式是能夠經過繼承來擴展存在的類,建立出新的類。可看下面的例子:瀏覽器

class Animal {
    name:string;
    constructor(theName: string) { this.name = theName; }
    move(meters: number = 0) {
        alert(this.name + " moved " + meters + "m.");
    }
}

class Snake extends Animal {
    constructor(name: string) { super(name); }
    move(meters = 5) {
        alert("Slithering...");        
        super.move(meters);
    }
}

class Horse extends Animal {
    constructor(name: string) { super(name); }
    move(meters = 45) {
        alert("Galloping...");        
        super.move(meters);
    }
}

var sam = new Snake("Sammy the Python");
var tom: Animal = new Horse("Tommy the Palomino");

sam.move();
tom.move(34);


這個例子展現的TypeScript中一些繼承特性在其餘語言中也能夠看到。這裏看到用 'extends' 關鍵字來建立一個子類。'Horse'和'Snake'子類都繼承自基類'Animal', 能夠訪問'Animal'的特性。 ide

這個例子也展現了子類中的方法可重載(override)基類中的方法,’Snake’和’Horse'子類都各自建立了一個‘move’方法來重載基類’Animal’的‘move’方法,這樣每一個子類就能夠實現特定的功能。函數

Private/Public修飾符

缺省爲Public

你可能注意到了在上例中咱們並無用'public'去描述類的每個成員使其可見。在相似於C#語言中,必須顯式地標註'public'關鍵字才能使得類的成員可見。可是在TypeScript中。每一個成員缺省就是public。this

有時咱們但願控制類成員不能被外部看到,就能夠將這些成員標記爲private。下面代碼中咱們但願隱藏上一章節中'Animal'類的name屬性:
編碼

class Animal {    
    private name:string;
    constructor(theName: string) { this.name = theName; }
    move(meters: number) {
        alert(this.name + " moved " + meters + "m.");
    }
}

理解私有(private)

TypeScript是一個結構化的類型系統。當比較兩個不一樣類型,不關心它們來自哪裏,若是每一個成員的類型都是兼容的,那麼就認爲這兩個類型也是兼容的。

當比較有'private'成員的類型時,就須要另外處理。當比較兩個類型時,若是一個類型擁有私有成員,那麼另一個類型必須包含源於同一個聲明的私有成員,才認爲這兩個類型是兼容的。

可參見下面例子來講明:

class Animal {    
    private name:string;
    constructor(theName: string) { this.name = theName; }
}

class Rhino extends Animal {
    constructor() { super("Rhino"); }
}

class Employee {    
    private name:string;
    constructor(theName: string) { this.name = theName; }	
}

var animal = new Animal("Goat");
var rhino = new Rhino();
var employee = new Employee("Bob");

animal = rhino;
animal = employee; //error: Animal and Employee are not compatible


上面的例子中有'Animal'和'Rhino'兩個類,'Rhino'是'Animal'的一個子類。同時咱們也定義了一個 'Employee'類,它和'Animal'類從形狀(shape)上看徹底相同。咱們建立了這三個類的實例,並相互賦值看看會發生什麼。由於'Animal'和'Rhino'共享'Animal'中相同的私有訪問聲明'private name: string',所以它們是兼容的。可是當咱們將'Employee'賦值給'Animal'時,獲得類型不兼容錯誤。雖然'Employee'也有一個私有成員'name',但它與 'Animal'中的私有成員'name'是不相同的,所以它們是不兼容的類型。

參數屬性(Parameter properties)

能夠經過關鍵字public’和’private建立快捷參數屬性方式,來建立並初始化類成員字段。參數屬性可讓咱們僅用一步就能夠建立和初始化類成員。下例是上例中咱們去掉了‘theName’,在構造函數中使用‘private name: string’參數,來建立'name'成員的同時初始化這個字段。

class Animal {
    constructor(private name: string) { }
    move(meters: number) {
        alert(this.name + " moved " + meters + "m.");
    }
}

這裏利用'private'爲參數屬性類建立了一個私有成員並初始化其值,對於public也相似。

訪問器(Accessors)

TypeScript支持利用getters/setters來控制對成員的訪問,這樣能夠更細粒度來控制類的成員訪問方式。

下面將一個類轉化爲使用'get'和'set'方式。先從沒有getters/setters的例子開始

class Employee {
    fullName: string;
}

var employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    alert(employee.fullName);
}

雖然直接設置'fullName'成員很方便,但若是有人隨意改變人名可能會形成麻煩。

在下邊,咱們但願將其轉化爲必須提供一個secret passcode,才能修改employee。經過'set'關鍵字來代替直接訪問fullName成員,相應地增長一個'get'關鍵字來訪問fullName成員

var passcode = "secret passcode";
class Employee {    
    private _fullName: string;

    get fullName(): string {        
        return this._fullName;
    }
	
    set fullName(newName: string) {        
        if (passcode && passcode == "secret passcode") {            
            this._fullName = newName;
        }        
        else {
            alert("Error: Unauthorized update of employee!");
        }
    }
}

var employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    alert(employee.fullName);
}

爲了證實如今的訪問器(Accessors)驗證了passcode值,能夠嘗試修改passcode的值,使其不匹配,就會獲得沒有權限更新employee的告警信息。

注意:訪問器須要設置編譯輸出爲ECMAScript 5。

靜態屬性(Static Properties)

到這裏,咱們只是討論了實例化類的成員,當實例化時就能夠經過對象來訪問成員。咱們也能夠建立類的靜態成員,是經過類來訪問而不是經過實例化對象來訪問。在下面這個例子中,咱們對原點('origin')成員使用’static’ 關鍵字,由於origin是全部grid的一個通用值。每一個實例經過類名爲前綴來訪問這個值。相似於在實例訪問成員前面用'this.',對靜態訪問成員前面用類名Grid。

class Grid {    
    static origin = {x: 0, y: 0};
    calculateDistanceFromOrigin(point: {x: number; y: number;}) {        
        var xDist = (point.x - Grid.origin.x);        
        var yDist = (point.y - Grid.origin.y);        
        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
    }
    constructor (public scale: number) { }
}

var grid1 = new Grid(1.0);  // 1x scale
var grid2 = new Grid(5.0);  // 5x scale
alert(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
alert(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));

高級技術

構造函數

當在TypeScript中聲明類的時候,實際上同時建立了多個聲明。首先是類實例的類型。

class Greeter {
    greeting: string;
    constructor(message: string) {        
        this.greeting = message;
    }
    greet() {        
        return "Hello, " + this.greeting;
    }
}

var greeter: Greeter;
greeter = new Greeter("world");
alert(greeter.greet());


在'var greeter: Greeter'這一行,咱們實際上正在用Greeter做爲類Greeter實例的類型。這對於其餘面嚮對象語言的編程人員來講是很天然的方式。

還建立了一個構造函數,這個函數是在用'new'來建立類的實例時調用的。下面看看上一個例子用JavaScript的編碼:

var Greeter = (function () {    
    function Greeter(message) {        
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {        
        return "Hello, " + this.greeting;
    };    
    return Greeter;
})();

var greeter;
greeter = new Greeter("world");
alert(greeter.greet());

這裏'var Greeter'被賦值爲構造函數。當調用'new'時調用這個構造函數,獲得類的實例。這個構造函數還包含了類的全部靜態成員。咱們能夠認爲每一個類都有實例部分和靜態部分。

咱們對上例稍作修改來展現這個差別:

class Greeter {    
    static standardGreeting = "Hello, there";
    greeting: string;
    greet() {        
        if (this.greeting) {            
            return "Hello, " + this.greeting;
        }        
        else {            
            return Greeter.standardGreeting;
        }
    }
}

var greeter1: Greeter;
greeter1 = new Greeter();
alert(greeter1.greet());

var greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";

var greeter2:Greeter = new greeterMaker();
alert(greeter2.greet());

這裏'greeter1'和前面的例子相似。咱們實例化'Greeter'類,而後調用此對象。這在前面的例子已經見過。

接下來咱們直接使用類。咱們建立了一個新變量'greeterMaker',這個變量保持了Greeter類的類型信息,換句話說是類的構造函數。這裏咱們使用'typeof Greeter',它給出Greeter類的類型,而不是實例類型。或者更準確地說,給出符號Greeter的類型就是構造函數的類型。這個類型包含Greeter全部的靜態成員,以及建立Greeter類實例的構造函數。咱們能夠用'new greeterMaker'來建立'Greeter'的實例,而後調用其方法。

將類用做接口

如上所述,類聲明建立了兩個東西:一個是類實例的類型,一個是構造函數。由於類建立了類型,因此咱們能夠將類用在使用接口的地方。

class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}

var point3d: Point3d = {x: 1, y: 2, z: 3};

參考資料

[1] http://www.typescriptlang.org/Handbook#classes

[2] TypeScript - Classes, 破狼blog, http://greengerong.com/blog/2014/11/17/typescript-classes/

[3] TypeScript系列1-簡介及版本新特性, http://my.oschina.net/1pei/blog/493012

[4] TypeScript手冊翻譯系列1-基礎類型, http://my.oschina.net/1pei/blog/493181

[5] TypeScript手冊翻譯系列2-接口, http://my.oschina.net/1pei/blog/493388

相關文章
相關標籤/搜索