Typescript學習筆記(一)

基礎類型

元組 Tuple

元組類型容許表示一個已知元素數量和類型的數組,各元素的類型沒必要相同 。好比,你能夠定義一對值分別爲 string和number類型的元組。javascript

let x: [string, number];
x = ['hello', 10]; // OK
x = [10, 'hello']; // Error
複製代碼

當訪問一個已知索引的元素,會獲得正確的類型:java

console.log(x[0].substr(1)); // OK
console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'
複製代碼

當訪問一個越界的元素,會使用聯合類型替代:數組

x[3] = 'world'; // OK, 字符串能夠賦值給(string | number)類型

console.log(x[5].toString()); // OK, 'string''number' 都有 toString

x[6] = true; // Error, 布爾不是(string | number)類型
複製代碼

枚舉

enum類型是對JavaScript標準數據類型的一個補充。使用枚舉類型能夠爲一組數值賦予友好的名字。bash

enum Color {Red=2, Green, Blue}
let c: Color = Color.Green;
複製代碼

默認狀況下,從0開始爲元素編號。 你也能夠手動的指定成員的數值,好比讓red=2。ide

枚舉類型提供的一個便利是你能夠由枚舉的值獲得它的名字函數

enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];
console.log(colorName);  // 顯示'Green'由於上面代碼裏它的值是2
複製代碼

Any

它表示當前對象的類型由具體的值的類型來肯定,它能夠適用於任何強類型。ui

Any類型的值能夠經過強制類型轉換將值轉換成目標類型this

Void

void類型像是與any類型相反,它表示沒有任何類型。當一個函數沒有返回值時,你一般會見到其返回值類型是 void,聲明一個void類型的變量沒有什麼大用,由於你只能爲它賦予undefined和null:spa

類型斷言

類型斷言比如其它語言裏的類型轉換,可是不進行特殊的數據檢查和解構。 它沒有運行時的影響,只是在編譯階段起做用。rest

類型斷言有兩種形式。 其一是「尖括號」語法:

let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;
複製代碼

另外一個爲as語法:

let someValue: any = "this is a string";

let strLength: number = (someValue as string).length;
複製代碼

變量聲明

let聲明不只是在循環裏引入了一個新的變量環境,而是針對 每次迭代都會建立這樣一個新做用域。 這就是咱們在使用當即執行的函數表達式時作的事,因此在 setTimeout例子裏咱們僅使用let聲明就能夠了。

for (let i = 0; i < 10 ; i++) {
    setTimeout(function() {console.log(i); }, 100 * i);
}
複製代碼

解構

let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2
複製代碼

能夠在數組裏使用...語法建立剩餘變量:

let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]
複製代碼

對象解構

class C {
  p = 12;
  m() {
  }
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!
複製代碼

接口

interface LabelledValue {
  label: string;
}

function printLabel(labelledObj: LabelledValue) {
  console.log(labelledObj.label);
}

let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
複製代碼

LabelledValue接口就比如一個名字,用來描述上面例子裏的要求。 它表明了有一個 label屬性且類型爲string的對象。 須要注意的是,咱們在這裏並不能像在其它語言裏同樣,說傳給 printLabel的對象實現了這個接口。咱們只會去關注值的外形。 只要傳入的對象知足上面提到的必要條件,那麼它就是被容許的。

還有一點值得提的是,類型檢查器不會去檢查屬性的順序,只要相應的屬性存在而且類型也是對的就能夠。

可選屬性

接口裏的屬性不全都是必需的。 有些是隻在某些條件下存在,或者根本不存在。

可選屬性在應用「optionbags」模式時很經常使用,即給函數傳入的參數對象中只有部分屬性賦值了。

帶有可選屬性的接口與普通的接口定義差很少,只是在可選屬性名字定義的後面加一個?符號。

只讀屬性

一些對象屬性只能在對象剛剛建立的時候修改其值。 你能夠在屬性名前用 readonly來指定只讀屬性:

interface Point {
    readonly x: number;
    readonly y: number;
}
複製代碼

TypeScript具備ReadonlyArray類型,它與Array類似,只是把全部可變方法去掉了,所以能夠確保數組建立後不再能被修改修改的話就報錯,

額外的屬性檢查

對象字面量會被特殊對待並且會通過額外屬性檢查,當將它們賦值給變量或做爲參數傳遞的時候。 若是一個對象字面量存在任何「目標類型」不包含的屬性時,你會獲得一個錯誤。

// error: 'colour' not expected in type 'SquareConfig'
let mySquare = createSquare({ colour: "red", width: 100 });
複製代碼

要避開錯誤的話可使用斷言

let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
複製代碼

函數類型

接口除了描述帶有屬性的普通對象外,接口也能夠描述函數類型。

爲了使用接口表示函數類型,咱們須要給接口定義一個調用簽名。 它就像是一個只有參數列表和返回值類型的函數定義參數列表裏的每一個參數都須要名字和類型。

interface SearchFunc {
  (source: string, subString: string): boolean;
}
複製代碼

下例展現瞭如何建立一個函數類型的變量,並將一個同類型的函數賦值給這個變量。

let mySearch: SearchFunc;
  mySearch = function(source: string, subString: string) {
  let result = source.search(subString);
  return result > -1;
}
複製代碼

可索引的類型

描述那些可以「經過索引獲得」的類型,好比a[10]或ageMap["daniel"]。 可索引類型具備一個 索引簽名,它描述了對象索引的類型,還有相應的索引返回值類型。

TypeScript支持兩種索引簽名:字符串和數字。 能夠同時使用兩種類型的索引,可是數字索引的返回值必須是字符串索引返回值類型的子類型。

這是由於當使用 number來索引時,JavaScript會將它轉換成string而後再去索引對象。 也就是說用 100(一個number)去索引等同於使用"100"(一個string)去索引,所以二者須要保持一致。

類類型

實現接口

interface ClockInterface {
    currentTime: Date;
}

class Clock implements ClockInterface {
    currentTime: Date;
    constructor(h: number, m: number) { }
}
複製代碼

在接口中描述一個方法,在類裏實現它,如同下面的setTime方法同樣:

interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}
複製代碼

繼承接口

和類同樣,接口也能夠相互繼承。 這讓咱們可以從一個接口裏複製成員到另外一個接口裏,能夠更靈活地將接口分割到可重用的模塊裏。

interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {
    sideLength: number;
}

let square = <Square>{}; square.color = "blue"; square.sideLength = 10; square.penWidth = 5.0; 複製代碼

混合類型

接口可以描述JavaScript裏豐富的類型。 由於JavaScript其動態靈活的特色,有時你會但願一個對象能夠同時具備上面提到的多種類型。

一個對象能夠同時作爲函數和對象使用,並帶有額外的屬性。

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = <Counter>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
複製代碼

接口繼承類

當接口繼承了一個類類型時,它會繼承類的成員但不包括其實現。 就好像接口聲明瞭全部類中存在的成員,但並無提供具體實現同樣。 接口一樣會繼承到類的private和protected成員。 這意味着當你建立了一個接口繼承了一個擁有私有或受保護的成員的類時,這個接口類型只能被這個類或其子類所實現(implement)。

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

let greeter = new Greeter("world");
複製代碼

咱們聲明一個 Greeter類。這個類有3個成員:一個叫作 greeting的屬性,一個構造函數和一個 greet方法。

你會注意到,咱們在引用任何一個類成員的時候都用了 this。 它表示咱們訪問的是類的成員。

最後一行,咱們使用 new構造了 Greeter類的一個實例。 它會調用以前定義的構造函數,建立一個 Greeter類型的新對象,並執行構造函數初始化它。

繼承

// 基類
class Animal {
    name: string;

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

    eat() {
        console.log(`${this.name} 吃食物。`);
    }
}

// 子類繼承基類
class Dog extends Animal {
    constructor(theName: string) {
        super(theName);
    }

    eat() {
        super.eat();
        console.log('而且吃的是狗糧。');
    }
}

class People extends Animal {
    constructor(theName: string) {
        super(theName);
    }

    // 子類重寫基類方法
    eat() {
        console.log(`${this.name} 拒絕吃狗糧。`);
    }
}

let animal = new Animal('動物');
animal.eat();

let dog: Animal;
dog = new Dog('狗');
dog.eat();

let people: Animal;
people = new People('人類');
people.eat();
複製代碼

從上面的例子能夠看到,子類經過extends關鍵字能夠繼承其餘類,經過super方法調用基類對應的方法,也能夠直接重寫基類的方法。

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);
複製代碼

咱們使用 extends關鍵字建立了 Animal的兩個子類: Horse和 Snake。

派生類包含了一個構造函數,它 必須調用 super(),它會執行基類的構造函數。 並且,在構造函數裏訪問 this的屬性以前,咱們必定要調用 super()。 這個是TypeScript強制執行的一條重要規則。

這個例子演示瞭如何在子類裏能夠重寫父類的方法。 Snake類和 Horse類都建立了 move方法,它們重寫了從 Animal繼承來的 move方法,使得 move方法根據不一樣的類而具備不一樣的功能。 注意,即便 tom被聲明爲 Animal類型,但由於它的值是 Horse,調用 tom.move(34)時,它會調用 Horse裏重寫的方法。

輸出以下

Slithering...
Sammy the Python moved 5m.
Galloping...
Tommy the Palomino moved 34m.
複製代碼

公共,私有與受保護的修飾符

默認爲 public

當成員被標記成 private時,它就不能在聲明它的類的外部訪問。

protected修飾符與 private修飾符的行爲很類似,但有一點不一樣, 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); // 錯誤
複製代碼

注意,由於 Employee是由 Person派生而來的。咱們不能在 Person類外使用 name,可是咱們仍然能夠經過 Employee類的實例方法訪問。

構造函數也能夠被標記成 protected。 這意味着這個類不能在包含它的類外被實例化,可是能被繼承。

存取器

TypeScript支持經過getters/setters來截取對對象成員的訪問。 它能幫助你有效的控制對對象成員的訪問。

靜態屬性

這裏咱們使用 Grid.來訪問靜態屬性。

經過static關鍵字能夠聲明類型的靜態屬性。

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}));
複製代碼

抽象類

// 抽象類
abstract class Animal {
    name: string;

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

    abstract eat();
}

// 子類繼承抽象類
class Dog extends Animal {
    constructor(theName: string) {
        super(theName);
    }

    eat() {
        console.log(`${this.name} 吃狗糧。`);
    }
}

let animal = new Animal('動物');      // 抽象類沒法實例化
animal.eat();

let dog: Animal;
dog = new Dog('狗');
dog.eat();
複製代碼

經過abstract關鍵字聲明抽象類和抽象方法,子類繼承抽象類後,須要實現抽象方法。一樣的,抽象類不能被實例化。

相關文章
相關標籤/搜索