TypeScript Class(類)

傳統的JavaScript注重用函數和基於原型的繼承來建立可複用的組件,但這可能讓用習慣面對對象方式的程序員感到棘手,由於他們的繼承和建立對象都是由類而來的。從JavaScript的下一個版本,ECMAScript 6開始,JavaScript程序員就可以用基於這種基於類的面對對象方式來建立編寫本身的程序了。在TypeScript中,不須要再等JavaScript的下一個版本就已經支持開發者使用這一技術了。程序員


讓咱們來看一個簡單的基於類的例子:編程

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

若是你以前有使用過C#或者Java,會以爲語法很是類似。咱們聲明一個新的類"Greeter"。這個類裏面有三個成員,一個名爲"greeting"的屬性,一個constructor和一個"greet"方法。
你會注意到,在類裏面當某一個成員使用了"this",意味着他訪問的是這個類的成員。
在最後一行中,咱們使用"new"來爲Greeter類構造一個實例。這將會調用以前定義的構造函數,而且建立一個新的Greeter類型的對象,而且執行構造函數來初始化這個對象。函數

繼承
在TypeScript中,咱們可使用常見的面向對象模式。固然,在基於類的編程中最基本的模式之一就是可以建立一個新的類,這個新的類繼承已有的類,並對已有的類作擴展。
來看一個例子:this

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"這個父類,而且對其特性進行了擴展。在這裏,咱們使用"extends"關鍵字來建立一個子類。你能夠看到,這裏"Horse"和"Snake"兩個子類都基於"Animal"這個基類而且獲取其特性。
例子也提現出在子類中能夠重寫基類中的方法以達到重寫後的方法是在這個子類中專用。這裏的"Horse"和"Snake"都建立了"move"這個方法,這樣就重寫了從基類繼承過來的move方法,而且在不一樣類中給"move"不一樣的方法。spa

公有和私有的修飾符
默認是public(公有)
你可能已經注意到了,在上面的例子中,咱們並未對類的任何可見成員使用"public"關鍵字進行修飾。相似C#語言,須要給成員使用"public"修飾符用來明確它是可見。在TypeScript中,每一個成員默認是"public"的。
你還能夠給成員標記上"private",這樣你就能夠控制在你的類以外哪些成員是可見。咱們能夠像這樣重寫上一節的"Animal"類:prototype

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

理解Private(私有)
TypeScript是個構造類型的系統。當咱們對兩個類型進行比較的時候,不管它們是從哪裏來,若是全部成員的類型都是兼容的,那麼咱們能夠認爲他們的類型也是兼容的。
當咱們比較的類型中含有"private"(私有)成員,則咱們就須要不一樣的對待了。兩個類型(假如是A和B)被認爲是兼容的,若是A類型含有一個私有成員,那麼B類型就必須也有一個私有成員而且與A類型的私有成員源自同一處聲明。
讓咱們用一個例子來更好的看看私有成員在實踐中如何運用:3d

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; // 錯誤: Animal 和 Employee 不兼容

在這個例子中,咱們有一個"Animal"和一個"Rhino","Rhino"是"Animal"的一個子類。咱們還有一個新的類"Employee",它看上去跟"Animal"類是徹底相同的。咱們給這些類分別建立實例,而且對他們進行相互賦值,看下將會發生什麼。由於"animal"和"rhino"的私有成員都是從"Animal"類定義的"private name: string"共享而來的,因此他們是兼容的。然而,"employee"的狀況卻不是這樣的。當咱們試圖將"employee"賦值給"animal",咱們獲得了一個錯誤,他們的類型是不兼容的。儘管"Employee"也有一個名稱是"name"的私有成員,但它和在"Animal"中的私有成員"name"仍是不相同的。code

參數屬性
關鍵字"public"和"private"經過建立參數屬性的方式給咱們提供了建立和初始化類的成員的便捷方式。這個特性讓你能夠一個步驟就建立和初始化成員。這裏有一個以前例子的進一步修改。注意咱們是如何在constructor中將"name"使用"private name: string"的便捷方式完整的建立並初始化成這個類的私有成員"name"的。對象

class Animal {
    constructor(private name: string) { }
    move(meters: number) {
        alert(this.name + " moved " + meters + "m.");
    }
}var goat = new Animal("Goat");
goat.move(25); // Goat moved 25 m.

經過這種方式使用"private"來建立和初始化私有成員,"public"也同樣。繼承

訪問器
TypeScript提供 getters/setters 的方式來攔截對於對象成員的訪問。它讓咱們能夠更精確的控制如何對對象成員的進行訪問。
讓咱們來將一個類改寫成用"get"和"set"。首先,咱們從一個沒有"get"和"set"的例子開始:

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

以上代碼容許咱們隨意設置fullName,可能咱們會以爲這樣比較直接和方便,但這麼爲所欲爲的改變名字也可能會致使問題。
在這個版本中,咱們將給被容許修改員工信息的用戶一個可用的密碼。在對fullName進行"set"訪問的以前,咱們會以檢查密碼來代替容許直接修改。咱們添加一個相應的"get"讓以前的例子依然能實現。

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

爲了證實如今訪問須要密碼,咱們能夠修改密碼,而後咱們會發現,當密碼不符合的時候會彈出提示"Error: Unauthorized update of employee!"(錯誤:沒有修改employee的權限)。

注意:訪問器須要咱們將文件以ECMAScript5編程輸出。

tsc --target ES5 your.ts

靜態屬性
到此爲止,咱們值談到類的實例成員,那些只有實例化後才初始化而且顯示的成員。咱們還能夠爲類的建立靜態成員,那些在類自己可見而非實在實例上可見。在這個例子中,咱們使用"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 規模var grid2 = new Grid(5.0);  // 5x 規模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"的新變量。這個變量保存了這個類,換種說法即保存了這個構造函數。這裏咱們使用"typeof Greeter",這麼作的話"greeterMaker"的類型就成了"Greeter"類的類型,而非"Greeter"的實例的類型("Greeter"類的實例類型爲"Greeter")。更準確的說,"給我Greeter類的類型",也就是構造函數的類型。這個類包含"Greeter"類的全部靜態成員和建立"Greeter"類的實例的構造函數。同以前的例子同樣,咱們對"greeterMaker"使用"new",用來建立"Greeter"的實例而且觸發。

將類看成接口同樣使用
正如咱們在上一節所說的,聲明一個類的同時會建立其餘兩個東西:這個類的實例類型和一個構造函數。由於類可以建立類型,因此在使用interface(接口)的地方均可以使用class(類)。

class Point {
    x: number;
    y: number;
}
interface Point3d extends Point {
    z: number;
}var point3d: Point3d = {x: 1, y: 2, z: 3};
相關文章
相關標籤/搜索