鍍金池/ 教程/ HTML/ 介紹
初始化項(xiàng)目結(jié)構(gòu)
聯(lián)合類型
介紹
介紹
介紹
編譯選項(xiàng)
TypeScript 1.6
介紹
介紹
發(fā)展路線圖
介紹
在MSBuild里使用編譯選項(xiàng)
可迭代性
TypeScript 1.3
介紹
介紹
TypeScript 1.1
變量聲明
即將到來(lái)的Angular 2框架是使用TypeScript開發(fā)的。 因此Angular和TypeScript一起使用非常簡(jiǎn)單方便
tsconfig.json
介紹
介紹
介紹
在MSBuild里使用編譯選項(xiàng)
使用TypeScript的每日構(gòu)建版本
新建工程
枚舉
三斜線指令
結(jié)合ASP.NET v5使用TypeScript
TypeScript里的this
介紹
TypeScript 1.4
編碼規(guī)范
介紹
模塊解析
ASP.NET 4
架構(gòu)概述
介紹
介紹
ASP.NET Core
TypeScript 1.8
介紹
介紹
創(chuàng)建簡(jiǎn)單工程
TypeScript 1.7
TypeScript 1.5
NPM包的類型
支持TypeScript的編輯器

介紹

傳統(tǒng)的JavaScript程序使用函數(shù)和基于原型的繼承來(lái)創(chuàng)建可重用的組件,但這對(duì)于熟悉使用面向?qū)ο蠓绞降某绦騿T來(lái)說(shuō)有些棘手,因?yàn)樗麄冇玫氖腔陬惖睦^承并且對(duì)象是從類構(gòu)建出來(lái)的。 從ECMAScript 2015,也就是ECMAScript 6,JavaScript程序?qū)⒖梢允褂眠@種基于類的面向?qū)ο蠓椒ā?在TypeScript里,我們?cè)试S開發(fā)者現(xiàn)在就使用這些特性,并且編譯后的JavaScript可以在所有主流瀏覽器和平臺(tái)上運(yùn)行,而不需要等到下個(gè)JavaScript版本。

下面看一個(gè)使用類的例子:

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

let greeter = new Greeter("world");

如果你使用過C#或Java,你會(huì)對(duì)這種語(yǔ)法非常熟悉。 我們聲明一個(gè)Greeter類。這個(gè)類有3個(gè)成員:一個(gè)叫做greeting的屬性,一個(gè)構(gòu)造函數(shù)和一個(gè)greet方法。

你會(huì)注意到,我們?cè)谝萌魏我粋€(gè)類成員的時(shí)候都用了this。 它表示我們?cè)L問的是類的成員。

最后一行,我們使用new構(gòu)造了Greeter類的一個(gè)實(shí)例。 它會(huì)調(diào)用之前定義的構(gòu)造函數(shù),創(chuàng)建一個(gè)Greeter類型的新對(duì)象,并執(zhí)行構(gòu)造函數(shù)初始化它。

繼承

在TypeScript里,我們可以使用常用的面向?qū)ο竽J健?當(dāng)然,基于類的程序設(shè)計(jì)中最基本的模式是允許使用繼承來(lái)擴(kuò)展一個(gè)類。

看下面的例子:

class Animal {
    name:string;
    constructor(theName: string) { this.name = theName; }
    move(distanceInMeters: number = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

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

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

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

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

這個(gè)例子展示了TypeScript中繼承的一些特征,與其它語(yǔ)言類似。 我們使用extends來(lái)創(chuàng)建子類。你可以看到HorseSnake類是基類Animal的子類,并且可以訪問其屬性和方法。

包含constructor函數(shù)的派生類必須調(diào)用super(),它會(huì)執(zhí)行基類的構(gòu)造方法。

這個(gè)例子演示了如何在子類里可以重寫父類的方法。 Snake類和Horse類都創(chuàng)建了move方法,重寫了從Animal繼承來(lái)的move方法,使得move方法根據(jù)不同的類而具有不同的功能。 注意,即使tom被聲明為Animal類型,因?yàn)樗闹凳?code>Horse,tom.move(34)調(diào)用Horse里的重寫方法:

Slithering...
Sammy the Python moved 5m.
Galloping...
Tommy the Palomino moved 34m.

公共,私有與受保護(hù)的修飾符

默認(rèn)為公有

在上面的例子里,我們可以自由的訪問程序里定義的成員。 如果你對(duì)其它語(yǔ)言中的類比較了解,就會(huì)注意到我們?cè)谥暗拇a里并沒有使用public來(lái)做修飾;例如,C#要求必須明確地使用public指定成員是可見的。 在TypeScript里,每個(gè)成員默認(rèn)為public的。

你也可以明確的將一個(gè)成員標(biāo)記成public。 我們可以用下面的方式來(lái)重寫上面的Animal類:

class Animal {
    public name: string;
    public constructor(theName: string) { this.name = theName; }
    public move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

理解private

當(dāng)成員被標(biāo)記成private時(shí),它就不能在聲明它的類的外部訪問。比如:

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

new Animal("Cat").name; // Error: 'name' is private;

TypeScript使用的是結(jié)構(gòu)性類型系統(tǒng)。 當(dāng)我們比較兩種不同的類型時(shí),并不在乎它們從哪兒來(lái)的,如果所有成員的類型都是兼容的,我們就認(rèn)為它們的類型是兼容的。

然而,當(dāng)我們比較帶有privateprotected成員的類型的時(shí)候,情況就不同了。 如果其中一個(gè)類型里包含一個(gè)private成員,那么只有當(dāng)另外一個(gè)類型中也存在這樣一個(gè)private成員, 并且它們是來(lái)自同一處聲明時(shí),我們才認(rèn)為這兩個(gè)類型是兼容的。 對(duì)于protected成員也使用這個(gè)規(guī)則。

下面來(lái)看一個(gè)例子,詳細(xì)的解釋了這點(diǎn):

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

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

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

這個(gè)例子中有AnimalRhino兩個(gè)類,RhinoAnimal類的子類。 還有一個(gè)Employee類,其類型看上去與Animal是相同的。 我們創(chuàng)建了幾個(gè)這些類的實(shí)例,并相互賦值來(lái)看看會(huì)發(fā)生什么。 因?yàn)?code>Animal和Rhino共享了來(lái)自Animal里的私有成員定義private name: string,因此它們是兼容的。 然而Employee卻不是這樣。當(dāng)把Employee賦值給Animal的時(shí)候,得到一個(gè)錯(cuò)誤,說(shuō)它們的類型不兼容。 盡管Employee里也有一個(gè)私有成員name,但它明顯不是Animal里面定義的那個(gè)。

理解protected

protected修飾符與private修飾符的行為很相似,但有一點(diǎn)不同,protected成員在派生類中仍然可以訪問。例如:

class Person {
    protected name: string;
    constructor(name: string) { this.name = name; }
}

class Employee extends Person {
    private department: string;

    constructor(name: string, department: string) {
        super(name)
        this.department = department;
    }

    public getElevatorPitch() {
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // error

注意,我們不能在Person類外使用name,但是我們?nèi)匀豢梢酝ㄟ^Employee類的實(shí)例方法訪問,因?yàn)?code>Employee是由Person派生出來(lái)的。

參數(shù)屬性

在上面的例子中,我們不得不定義一個(gè)受保護(hù)的成員name和一個(gè)構(gòu)造函數(shù)參數(shù)theNamePerson類里,并且立刻給nametheName賦值。 這種情況經(jīng)常會(huì)遇到。參數(shù)屬性可以方便地讓我們?cè)谝粋€(gè)地方定義并初始化一個(gè)成員。 下面的例子是對(duì)之前Animal類的修改版,使用了參數(shù)屬性:

class Animal {
    constructor(private name: string) { }
    move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

注意看我們是如何舍棄了theName,僅在構(gòu)造函數(shù)里使用private name: string參數(shù)來(lái)創(chuàng)建和初始化name成員。 我們把聲明和賦值合并至一處。

參數(shù)屬性通過給構(gòu)造函數(shù)參數(shù)添加一個(gè)訪問限定符來(lái)聲明。 使用private限定一個(gè)參數(shù)屬性會(huì)聲明并初始化一個(gè)私有成員;對(duì)于publicprotected來(lái)說(shuō)也是一樣。

存取器

TypeScript支持getters/setters來(lái)截取對(duì)對(duì)象成員的訪問。 它能幫助你有效的控制對(duì)對(duì)象成員的訪問。

下面來(lái)看如何把一類改寫成使用getset。 首先是一個(gè)沒用使用存取器的例子。

class Employee {
    fullName: string;
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    console.log(employee.fullName);
}

我們可以隨意的設(shè)置fullName,這是非常方便的,但是這也可能會(huì)帶來(lái)麻煩。

下面這個(gè)版本里,我們先檢查用戶密碼是否正確,然后再允許其修改employee。 我們把對(duì)fullName的直接訪問改成了可以檢查密碼的set方法。 我們也加了一個(gè)get方法,讓上面的例子仍然可以工作。

let 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 {
            console.log("Error: Unauthorized update of employee!");
        }
    }
}

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

我們可以修改一下密碼,來(lái)驗(yàn)證一下存取器是否是工作的。當(dāng)密碼不對(duì)時(shí),會(huì)提示我們沒有權(quán)限去修改employee。

注意:若要使用存取器,要求設(shè)置編譯器輸出目標(biāo)為ECMAScript 5或更高。

靜態(tài)屬性

到目前為止,我們只討論了類的實(shí)例成員,那些僅當(dāng)類被實(shí)例化的時(shí)候才會(huì)被初始化的屬性。 我們也可以創(chuàng)建類的靜態(tài)成員,這些屬性存在于類本身上面而不是類的實(shí)例上。 在這個(gè)例子里,我們使用static定義origin,因?yàn)樗撬芯W(wǎng)格都會(huì)用到的屬性。 每個(gè)實(shí)例想要訪問這個(gè)屬性的時(shí)候,都要在origin前面加上類名。 如同在實(shí)例屬性上使用this.前綴來(lái)訪問屬性一樣,這里我們使用Grid.來(lái)訪問靜態(tài)屬性。

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

let grid1 = new Grid(1.0);  // 1x scale
let grid2 = new Grid(5.0);  // 5x scale

console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));

抽象類

抽象類是供其它類繼承的基類。 他們一般不會(huì)直接被實(shí)例化。 不同于接口,抽象類可以包含成員的實(shí)現(xiàn)細(xì)節(jié)。 abstract關(guān)鍵字是用于定義抽象類和在抽象類內(nèi)部定義抽象方法。

abstract class Animal {
    abstract makeSound(): void;
    move(): void {
        console.log('roaming the earch...');
    }
}

抽象類中的抽象方法不包含具體實(shí)現(xiàn)并且必須在派生類中實(shí)現(xiàn)。 抽象方法的語(yǔ)法與接口方法相似。 兩者都是定義方法簽名不包含方法體。 然而,抽象方法必須使用abstract關(guān)鍵字并且可以包含訪問符。

abstract class Department {

    constructor(public name: string) {
    }

    printName(): void {
        console.log('Department name: ' + this.name);
    }

    abstract printMeeting(): void; // 必須在派生類中實(shí)現(xiàn)
}

class AccountingDepartment extends Department {

    constructor() {
        super('Accounting and Auditing'); // constructors in derived classes must call super()
    }

    printMeeting(): void {
        console.log('The Accounting Department meets each Monday at 10am.');
    }

    generateReports(): void {
        console.log('Generating accounting reports...');
    }
}

let department: Department; // ok to create a reference to an abstract type
department = new Department(); // error: cannot create an instance of an abstract class
department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass
department.printName();
department.printMeeting();
department.generateReports(); // error: method doesn't exist on declared abstract type

高級(jí)技巧

構(gòu)造函數(shù)

當(dāng)你在TypeScript里定義類的時(shí)候,實(shí)際上同時(shí)定義了很多東西。 首先是類的實(shí)例的類型。

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

let greeter: Greeter;
greeter = new Greeter("world");
console.log(greeter.greet());

在這里,我們寫了let greeter: Greeter,意思是Greeter類實(shí)例的類型是Greeter。 這對(duì)于用過其它面向?qū)ο笳Z(yǔ)言的程序員來(lái)講已經(jīng)是老習(xí)慣了。

我們也創(chuàng)建了一個(gè)叫做構(gòu)造函數(shù)的值。 這個(gè)函數(shù)會(huì)在我們使用new創(chuàng)建類實(shí)例的時(shí)候被調(diào)用。 下面我們來(lái)看看,上面的代碼被編譯成JavaScript后是什么樣子的:

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

let greeter;
greeter = new Greeter("world");
console.log(greeter.greet());

上面的代碼里,let Greeter將被賦值為構(gòu)造函數(shù)。 當(dāng)我們使用new并執(zhí)行這個(gè)函數(shù)后,便會(huì)得到一個(gè)類的實(shí)例。 這個(gè)構(gòu)造函數(shù)也包含了類的所有靜態(tài)屬性。 換個(gè)角度說(shuō),我們可以認(rèn)為類具有實(shí)例部分與靜態(tài)部分這兩個(gè)部分。

讓我們來(lái)改寫一下這個(gè)例子,看看它們之前的區(qū)別:

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

let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet());

let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";
let greeter2:Greeter = new greeterMaker();
console.log(greeter2.greet());

這個(gè)例子里,greeter1與之前看到的一樣。 我們實(shí)例化Greeter類,并使用這個(gè)對(duì)象。 與我們之前看到的一樣。

再之后,我們直接使用類。 我們創(chuàng)建了一個(gè)叫做greeterMaker的變量。 這個(gè)變量保存了這個(gè)類或者說(shuō)保存了類構(gòu)造函數(shù)。 然后我們使用typeof Greeter,意思是取Greeter類的類型,而不是實(shí)例的類型。 或者更確切的說(shuō),"告訴我Greeter標(biāo)識(shí)符的類型",也就是構(gòu)造函數(shù)的類型。 這個(gè)類型包含了類的所有靜態(tài)成員和構(gòu)造函數(shù)。 之后,就和前面一樣,我們?cè)?code>greeterMaker上使用new,創(chuàng)建Greeter的實(shí)例。

把類當(dāng)做接口使用

如上一節(jié)里所講的,類定義會(huì)創(chuàng)建兩個(gè)東西:類實(shí)例的類型和一個(gè)構(gòu)造函數(shù)。 因?yàn)轭惪梢詣?chuàng)建出類型,所以你能夠在可以使用接口的地方使用類。

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

interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};
上一篇:介紹下一篇:tsconfig.json