TypeScript

基本類型

  • string 字符串
  • boolean 布爾
  • number 數值
  • null
  • undefined
  • void
  • any 任意
  1. 與void的區別是,undefined 和 null 是全部類型的子類型。也就是說 undefined 類型的變量,能夠賦值給 number 類型的變量。
  2. 變量若是在聲明的時候,未指定其類型,那麼它會被識別爲任意值類型。

類型推論

定義的時候有賦值,將會推斷成當前值的類型。web

let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

定義的時候沒有賦值,無論以後有沒有賦值,都會被推斷成any類型而徹底不被類型檢查。數組

let myFavoriteNumber;

myFavoriteNumber =  'seven';

myFavoriteNumber =  7;

聯合類型

聯合類型的變量在被賦值的時候,會根據類型推論的規則推斷出一個類型:函數

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length); // 5
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // 編譯時報錯

// index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'.

上例中,第二行的 myFavoriteNumber 被推斷成了 string,訪問它的 length 屬性不會報錯。
而第四行的myFavoriteNumber被推斷成了number,訪問它的 length 屬性時就報錯了。ui

接口

任意屬性和只讀屬性
  1. 一旦定義了任意屬性,那麼肯定屬性和可選屬性的類型都必須是它的類型的子集
  2. 只讀的約束存在於第一次給對象賦值的時候,而不是第一次給只讀屬性賦值的時候
interface  Person  {
    readonly id:  number; //只讀屬性
    name:  string;
    age?:  number; // 可選屬性的類型必須是任意屬性類型的子集
    [propName:  string]:  string; // 定義任意屬性
}
let tom: Person =  {
    id:  89757, // 只讀屬性須要在此時定義
    name:  'Tom',
    age:  25,
    gender:  'male'
};

tom.id \=  89757; // 只讀屬性不能在此時定義,會報錯。

// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.

// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.

// Index signatures are incompatible.

// Type 'string | number' is not assignable to type 'string'.

// Type 'number' is not assignable to type 'string'.

// index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'.

// Property 'id' is missing in type '{ name: string; gender: string; }'.

// index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

數組類型

  • 「類型 + 方括號」表示法:let fibonacci: number[] = [1, 1, 2, 3, 5];
  • 數組泛型:``let fibonacci: Array<number> = [1, 1, 2, 3, 5];
  • 用接口表示數組:
interface  NumberArray  {
    [index:  number]:  number;
}
  • 類數組
function sum() {
    let args: {
        [index: number]: number;
        length: number;
        callee: Function;
    } = arguments;
}
  • any類型:let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];

函數類型

function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

然而這樣有一個缺點,就是不可以精確的表達,輸入爲數字的時候,輸出也應該爲數字,輸入爲字符串的時候,輸出也應該爲字符串。this

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

TypeScript 會優先從最前面的函數定義開始匹配,因此多個函數定義若是有包含關係,須要優先把精確的定義寫在前面。3d

類型斷言

語法:值 as 類型<類型>值
代碼中使用any類型,咱們也能夠選擇改進它,經過類型斷言及時的把 any斷言爲精確的類型,亡羊補牢,使咱們的代碼向着高可維護性的目標發展。code

function getCacheData(key: string): any { // 函數返回any
    return (window as any).cache[key];
}

interface Cat {
    name: string;
    run(): void;
}

const tom = getCacheData('tom') as Cat; // 在使用的時候將其斷言成具體類型。
tom.run();

總結:對象

  • 聯合類型能夠被斷言爲其中一個類型
  • 父類能夠被斷言爲子類
  • 任何類型均可以被斷言爲 any
  • any 能夠被斷言爲任何類型
  • 要使得 A 可以被斷言爲 B,只須要 A 兼容 BB 兼容 A 便可

雙重斷言

as any as Foo繼承

interface Cat {
    run(): void;
}
interface Fish {
    swim(): void;
}

function testCat(cat: Cat) {
    return (cat as any as Fish); // 雙重斷言
}

除非無可奈何,千萬別用雙重斷言。接口

類型斷言 vs 類型轉換

類型斷言只會影響 TypeScript 編譯時的類型,類型斷言語句在編譯結果中會被刪除:

function toBoolean(something: any): boolean {
    return something as boolean;
}

toBoolean(1);
// 返回值爲 1
function toBoolean(something: any): boolean {
    return Boolean(something);
}

toBoolean(1);
// 返回值爲 true

類型斷言 vs 類型聲明

爲了增長代碼的質量,咱們最好優先使用類型聲明,這也比類型斷言的 as 語法更加優雅。

interface Animal {
    name: string;
}
interface Cat {
    name: string;
    run(): void;
}

const animal: Animal = {
    name: 'tom'
};
let tom = animal as Cat;

let tom: Cat = animal; // 會報錯
// error TS2741: Property 'run' is missing in type 'Animal' but required in type 'Cat'
  • animal 斷言爲 Cat,只須要知足 Animal 兼容 CatCat 兼容 Animal 便可
  • animal 賦值給 tom,須要知足 Cat 兼容 Animal 才行

類型斷言 vs 範型

function getCacheData<T>(key: string): T {
    return (window as any).cache[key];
}

interface Cat {
    name: string;
    run(): void;
}

const tom = getCacheData<Cat>('tom');
tom.run();

經過給 getCacheData 函數添加了一個範型 <T>,咱們能夠更加規範的實現對 getCacheData 返回值的約束,這也同時去除掉了代碼中的 any,是最優的一個解決方案。

進階

類型別名

類型別名經常使用於聯合類型。

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

字符串字面量類型

type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
    // do something
}

handleEvent(document.getElementById('hello'), 'scroll');  // 沒問題
handleEvent(document.getElementById('world'), 'dbclick'); // 報錯,event 不能爲 'dbclick'

// index.ts(7,47): error TS2345: Argument of type '"dbclick"' is not assignable to parameter of type 'EventNames'.

元祖

定義一對值分別爲 stringnumber 的元組:

let tom: [string, number];
tom[0] = 'Tom';
tom[1] = 25;

tom[0].slice(1);
tom[1].toFixed(2);

可是當直接對元組類型的變量進行初始化或者賦值的時候,須要提供全部元組類型中指定的項。

let tom: [string, number];
tom = ['Tom'];

// Property '1' is missing in type '[string]' but required in type '[string, number]'.

  • 類(Class):定義了一件事物的抽象特色,包含它的屬性和方法
  • 對象(Object):類的實例,經過 new 生成
  • 面向對象(OOP)的三大特性:封裝、繼承、多態
  • 封裝(Encapsulation):將對數據的操做細節隱藏起來,只暴露對外的接口。外界調用端不須要(也不可能)知道細節,就能經過對外提供的接口來訪問該對象,同時也保證了外界沒法任意更改對象內部的數據
  • 繼承(Inheritance):子類繼承父類,子類除了擁有父類的全部特性外,還有一些更具體的特性
  • 多態(Polymorphism):由繼承而產生了相關的不一樣的類,對同一個方法能夠有不一樣的響應。好比 CatDog 都繼承自 Animal,可是分別實現了本身的 eat 方法。此時針對某一個實例,咱們無需瞭解它是 Cat 仍是 Dog,就能夠直接調用 eat 方法,程序會自動判斷出來應該如何執行 eat
  • 存取器(getter & setter):用以改變屬性的讀取和賦值行爲
  • 修飾符(Modifiers):修飾符是一些關鍵字,用於限定成員或類型的性質。好比 public 表示公有屬性或方法
  • 抽象類(Abstract Class):抽象類是供其餘類繼承的基類,抽象類不容許被實例化。抽象類中的抽象方法必須在子類中被實現
  • 接口(Interfaces):不一樣類之間公有的屬性或方法,能夠抽象成一個接口。接口能夠被類實現(implements)。一個類只能繼承自另外一個類,可是能夠實現多個接口

類實現接口

interface Alarm {
    alert(): void;
}

class Door {
}
// 防盜門 繼承 門 繼承 接口告警
class SecurityDoor extends Door implements Alarm {
    alert() {
        console.log('SecurityDoor alert');
    }
}
// 車 繼承 接口告警
class Car implements Alarm {
    alert() {
        console.log('Car alert');
    }
}

一個類能夠實現多個接口:

interface Alarm {
    alert(): void;
}

interface Light {
    lightOn(): void;
    lightOff(): void;
}

class Car implements Alarm, Light {
    alert() {
        console.log('Car alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}

接口繼承接口

interface Alarm {
    alert(): void;
}

interface LightableAlarm extends Alarm {
    lightOn(): void;
    lightOff(): void;
}

接口繼承類

常見的面嚮對象語言中,接口是不能繼承類的,可是在 TypeScript 中倒是能夠的:

class Point {
    x: number;
    y: number;
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
}

interface Point3d extends Point {
    z: number;
}

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

爲何 TypeScript 會支持接口繼承類呢?
實際上,當咱們在聲明 class Point 時,除了會建立一個名爲 Point 的類以外,同時也建立了一個名爲 Point 的類型(實例的類型),因此咱們既能夠將 Point 當作一個類來用(使用 new Point 建立它的實例),也能夠將 Point 當作一個類型來用(使用 : Point 表示參數的類型):

class Point {
    x: number;
    y: number;
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
}

function printPoint(p: Point) { // 看成參數的類型
    console.log(p.x, p.y);
}

printPoint(new Point(1, 2)); // 看成類,建立實例

值得注意的是,PointInstanceType 相比於 Point,缺乏了 constructor 方法,這是由於聲明 Point 類時建立的 Point 類型是不包含構造函數的。另外,除了構造函數是不包含的,靜態屬性或靜態方法也是不包含的(實例的類型固然不該該包括構造函數、靜態屬性或靜態方法)。
我的理解:接口至關於類產生的類型

class Point {
    /** 靜態屬性,座標系原點 */
    static origin = new Point(0, 0);
    /** 靜態方法,計算與原點距離 */
    static distanceToOrigin(p: Point) {
        return Math.sqrt(p.x * p.x + p.y * p.y);
    }
    /** 實例屬性,x 軸的值 */
    x: number;
    /** 實例屬性,y 軸的值 */
    y: number;
    /** 構造函數 */
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
    /** 實例方法,打印此點 */
    printPoint() {
        console.log(this.x, this.y);
    }
}

interface PointInstanceType {
    x: number;
    y: number;
    printPoint(): void;
}

let p1: Point;
let p2: PointInstanceType;

泛型

泛型(Generics)是指在定義函數、接口或類的時候,不預先指定具體的類型,而在使用的時候再指定類型的一種特性。

function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}

swap([7, 'seven']); // ['seven', 7]

泛型約束

在函數內部使用泛型變量的時候,因爲事先不知道它是哪一種類型,因此不能隨意的操做它的屬性或方法:

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);
    return arg;
}

// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'.
interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}

泛型接口

interface CreateArrayFunc<T> {
    (length: number, value: T): Array<T>;
}

let createArray: CreateArrayFunc<any>; //此時在使用泛型接口的時候,須要定義泛型的類型。
createArray = function<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']

泛型類

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

泛型參數的默認類型

在 TypeScript 2.3 之後,咱們能夠爲泛型中的類型參數指定默認類型。當使用泛型時沒有在代碼中直接指定類型參數,從實際值參數中也沒法推測出時,這個默認類型就會起做用。

function createArray<T = string>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}
相關文章
相關標籤/搜索