TypeScript 學習筆記

簡介

TypeScript 是 JavaScript 的一個超集,主要提供了 類型系統(tǒng) 和對 ES6 的支持,由 Microsoft 開發(fā)。

應(yīng)用:vue3.0,angular2.0,vscode...

  1. 編譯型語言:編譯為 js 后運行,單獨無法運行;
  2. 強類型語言;
  3. 面向?qū)ο蟮恼Z言;

優(yōu)勢

  1. 類型系統(tǒng)實際上是最好的文檔,大部分的函數(shù)看看類型的定義就可以知道如何使用;
  2. 可以在編譯階段就發(fā)現(xiàn)大部分錯誤,這總比在運行時候出錯好;
  3. 增強了編輯器和 IDE 的功能,包括代碼補全、接口提示、跳轉(zhuǎn)到定義、重構(gòu)等;

總結(jié):TypeSctipt增加了代碼的可讀性和可維護性。

安裝

需要有node環(huán)境,通過npm安裝

npm install -g typescript

編碼

在線編譯預覽 TS

使用 .ts 文件擴展名, 使用 typescript 編寫使用 React 時,使用 .tsx 擴展名。

使用 : 指定變量的類型,: 的前后有沒有空格都可以;

function sayHello(person: string) {
    return 'Hello, ' + person;
}

let user = 'Tom';
console.log(sayHello(user));

編譯

使用tsc 命令可編譯 .ts 文件, 生成一個同名 .js 文件;編譯的時候即使報錯了,還是會生成編譯結(jié)果(.js),可通過 tsconfig.json 文件配置

tsc demo.ts

基礎(chǔ)類型

布爾值 boolean

let isDone: boolean = false;

注意,使用構(gòu)造函數(shù) Boolean 創(chuàng)造的對象不是布爾值

let newBool: boolean = new Boolean(true);
// 編譯報錯: 不能將類型“Boolean”分配給類型“boolean”。“boolean”是基元,但“Boolean”是包裝器對象。如可能首選使用“boolean”。ts(2322)

數(shù)字 number

let number: number = 6;
let notANumber: number = NaN;

字符串 string

let  string: string = 'Tom';
let sentence: string = `my name is ${aString}`;

空值 void

void 類型的變量只能賦值為 undefined 和 null

let unusable: void = undefined;

可以用 void 表示沒有任何返回值的函數(shù)

function alertName(): void {
  alert('My name is Tom');
}

null 和 undefined

undefined 類型的變量只能被賦值為 undefined,null 類型的變量只能被賦值為 null

let u: undefined = undefined;
let n: null = null;

與 void 的區(qū)別是,undefined 和 null 是所有類型的子類型。也就是說 undefined 類型的變量,可以賦值給 number 類型的變量:

let u: undefined;
let num: number = u;
let num2:number = undefined;
// 編譯合法 undefined是number的子類型

let unm2: void;
let num3: number = unm2;
// => 不合法 (void不是number的子類型)

任意值 any

any 用來表示允許賦值為任意類型

let anyType:any = 'seven';
anyType = 7;

在任意值上訪問任何屬性和方法都是允許的,即不做類型檢查

let anyType:any = 'seven';
console.log(anyType.name().age) 
// => 允許編譯,但是js執(zhí)行會報錯

變量如果在聲明的時候,未指定其類型, 也沒有賦值, 那么它會被推斷(類型推論)為任意值類型而完全不被類型檢查

let something; 
// 等價于 let something: any;
something = 'seven';
something = 7;

數(shù)組

可理解為相同類型的一組數(shù)據(jù),數(shù)組類型有多種定義方式

1,類型 + 方括號( type [ ] )

這種方式定義的數(shù)組項中不允許出現(xiàn)其他的類型

let list: number[] = [1, 2, 3];

2,數(shù)組泛型 Array < type >

let list: Array<number> = [1, 2, 3];

元祖 Tuple

元組類型允許表示一個已知元素數(shù)量和類型的數(shù)組,各元素的類型不必相同,簡單理解為可定義一組不同類型的數(shù)據(jù):

let arr:[string, number] = ['name', 20];
console.log(arr[0]); 
// => 'name' 

越界元素:當訪問超出元祖長度的元素時,它的類型會被限制為元祖中每個類型的聯(lián)合類型

let arr:[string, number] = ['name', 20];
arr[0] = 'age';
arr[2] = 'string';
arr[3] = 40;
arr[4] = true; // 編譯報錯

枚舉 enum

['en?m]

枚舉類型用于取值被限定在一定范圍內(nèi)的場景,如一周只有7天,一年只有4季等。

枚舉初始化

枚舉初始化可以理解為給枚舉成員賦值。每個枚舉成員都需要帶有一個值,在未賦值的情況下, 枚舉成員會被賦值為從 0 開始, 步長為 1 遞增的數(shù)字:

enum Weeks {Mon, Tue, Wed, Thu, Fri, Sat, Sun};

console.log(Weeks['Mon']); // => 0
console.log(Weeks[0]); // => 'Mon'
console.log(Weeks.Tue); // => 1

手動賦值時, 未賦值的枚舉成員會接著上一個枚舉項遞增(初始化):

enum Weeks {
    Mon, Tue, Wed, Thu = 2, Fri, Sat = -1.5, Sun
};

console.log(Weeks['Mon']); // => 0
console.log(Weeks.Wed); // => 2
console.log(Weeks.Thu); // => 2
console.log(Weeks.Fri); // => 3
console.log(Weeks.Sun); // => -0.5

上例中,未手動賦值的 Wed 和手動賦值的 Thu 取值重復了,但是 TypeScript 并不會報錯,該種情況可能會引起取值錯誤,所以使用的時候最好避免出現(xiàn)取值重復的情況。

TypeScript 支持 數(shù)字 的和基于字符串的枚舉。

數(shù)字枚舉

enum Weeks {
    Sun, Mon, Tue, Wed, Thu, Fri, Sat
};

字符串枚舉

enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}

異構(gòu)枚舉(Heterogeneous enums)

可以混合字符串和數(shù)字,但通常不這么做

enum Gender {
    Male = 0,
    Female = "1",
}

常量成員和計算所得成員

枚舉成員的值可以是 常量 或 計算出來的。

上面所舉的例子都是常量成員,官網(wǎng)定義如下:

當滿足以下條件時,枚舉成員被當作是常數(shù):

  • 不具有初始化函數(shù)并且之前的枚舉成員是常數(shù)。在這種情況下,當前枚舉成員的值為上一個枚舉成員的值加 1。但第一個枚舉元素是個例外。如果它沒有初始化方法,那么它的初始值為 0
  • 枚舉成員使用常數(shù)枚舉表達式初始化。常數(shù)枚舉表達式是 TypeScript 表達式的子集,它可以在編譯階段求值。當一個表達式滿足下面條件之一時,它就是一個常數(shù)枚舉表達式:
    • 數(shù)字字面量
    • 引用之前定義的常數(shù)枚舉成員(可以是在不同的枚舉類型中定義的)如果這個成員是在同一個枚舉類型中定義的,可以使用非限定名來引用
    • 帶括號的常數(shù)枚舉表達式
    • +, -, ~ 一元運算符應(yīng)用于常數(shù)枚舉表達式
    • +, -, *, /, %, <<, >>, >>>, &, |, ^ 二元運算符,常數(shù)枚舉表達式做為其一個操作對象。若常數(shù)枚舉表達式求值后為 NaN 或 Infinity,則會在編譯階段報錯

所有其它情況的枚舉成員被當作是需要計算得出的值。

常量枚舉 const enum

常數(shù)枚舉與普通枚舉的區(qū)別是,它會在編譯階段被刪除,并且不能包含計算成員。

const enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

編譯后:

var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];

外部枚舉 declare enum

外部枚舉與聲明語句一樣,常出現(xiàn)在聲明文件中。

declare enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

編譯后:

var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

同時使用 declareconst 也是可以的,編譯結(jié)果同常量枚舉一致。

never

永遠不存在值的類型,一般用于錯誤處理函數(shù)。

// 返回never的函數(shù)必須存在無法達到的終點
function error(message: string): never {
    throw new Error(message);
}

symbol

自ECMAScript 2015起,symbol成為了一種新的原生類型,就像 numberstring 一樣。

symbol類型的值是通過Symbol構(gòu)造函數(shù)創(chuàng)建的。

let sym1 = Symbol();

Symbols是不可改變且唯一的。

let sym2 = Symbol("key");
let sym3 = Symbol("key");
sym2 === sym3; // false, symbols是唯一的

更多用法參看 阮一峰ES6的symbol

object

object表示非原始類型,也就是除numberstringbooleansymbolnullundefined之外的類型。

function create(o: object | null): void;

create({ prop: 0 }); // OK
create(null); // OK

create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error

內(nèi)置對象

JavaScript 中有很多內(nèi)置對象,它們可以直接在 TypeScript 中當做定義好了的類型。

ECMAScript 的內(nèi)置對象

BooleanErrorDateRegExp 等。更多的內(nèi)置對象,可以查看 MDN 的文檔

let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;

DOM 和 BOM 的內(nèi)置對象

DocumentHTMLElementEventNodeList 等。

let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
  // Do something
});

類型推論

變量申明如果沒有明確的指定類型,那么 TypeScript 會依照類型推論的規(guī)則推斷出一個類型

let string = 'seven';
// 等價于 let string: string = 'seven';
string = 4;
// 編譯報錯: error TS2322: Type 'number' is not assignable to type 'string'

變量聲明但是未賦值,會推論為 any

let x;
x = 1;
x = 'aaa'

聯(lián)合類型

表示取值可以為多種類型中的一種,使用 | 分隔每個類型

let stringOrNumber:string | number;
stringOrNumber = 'seven';

當 TypeScript 不確定一個聯(lián)合類型的變量到底是哪個類型的時候, 我們只能訪問此聯(lián)合類型的所有類型里共有的屬性或方法

function getString(something: string | number): string {
  // toString 是 string類型 和 number類型 的共有屬性
  return something.toString();
}

function getLength(something: string | number): number {
  return something.length;
  // => 編譯報錯: length 不是 string類型 和 number類型 的共有屬性, 所以報錯
}

類型斷言

類型斷言(Type Assertion)可以用來手動指定一個值的類型。

類型斷言有2種形式:

1,<類型>值 ( 尖括號語法 )

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

let strLength: number = (<string>someValue).length;

2,值 as 類型 ( as 語法 )

當使用 tsx 時,只有 as語法斷言是被允許的

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

let strLength: number = (someValue as string).length;

在上述 聯(lián)合類型 的例子中, getLength 方法會編譯報錯,此時我們可以使用類型斷言,將 something 斷言成 string 就不會報錯了:

function getLength(something: string | number): number {
    if ((<string>something).length) {
        // 將 something 斷言為 string類型
        return (<string>something).length;
    } else {
        return something.toString().length;
    }
}

注意 : 類型斷言不是類型轉(zhuǎn)換,斷言成一個聯(lián)合類型中不存在的類型是不允許的:

function toBoolean(something: string | number): boolean {
    return <boolean>something;
    // => 報錯
}

類型別名 type

類型別名用來給一個類型起個新名字,多用于聯(lián)合類型:

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

type 聲明可以定義聯(lián)合類型,基本類型等多種類型,而 interface 只能定義對象類型

字符串字面量類型

字符串字面量類型用來約束取值只能是某幾個字符串中的一個。

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'

接口 Interfaces

接口(Interfaces)是一個很重要的概念,可以理解為一種規(guī)范或者約束,用來描述 對象(object) 的形狀 或者對 類(class) 的行為 進行抽象。對類的行為抽象將在后面 類與接口 一章中介紹,下面主要介紹對對象的形狀進行描述。

接口定義

使用 interface 定義接口, 接口名稱一般首字母大寫,定義接口的時候,只定義聲明即可,不包含具體內(nèi)容:

// 定義一個接口 Person
interface Person {
  name: string;
  age: number;
}

// 定義一個個變量,它的類型是 Person
let tom: Person = {
  name: 'Tom',
  age: 25
};

實現(xiàn)接口的時候,要實現(xiàn)里面的內(nèi)容,定義的變量比接口少了或多了屬性都是不允許的:

let tom: Person = {
  name: 'tom'
}
// => 編譯報錯,少了age屬性

可選屬性

使用 ? 代表可選屬性, 即該屬性可以不存在, 但不允許添加未定義的屬性

interface Person {
  name: string;
  age?: number;
}
let tom: Person = {
  name: 'tom'
}
// age是可選屬性

任意屬性

定義了任意屬性后可以添加未定義的屬性,并可以指定屬性值的類型

interface Person03 {
  name: string;
  age?: number;
  [propName: string]: any;
}
let tom04: Person03 = {
  name: 'Tom',
  age: 25,
  gender: 'male'
};

定義了任意屬性,那么確定屬性和可選屬性都必須是它的子屬性

interface Person {
  name: string;
  age?: number;
  [propName: string]: string;
}
// 編譯報錯:Person定義了一個任意屬性,其值為string類型。則Person的所有屬性都必須為string類型,而age為number類型

只讀屬性 readonly

interface Person {
  readonly id: number;
  name: string;
  age?: number;
  [propName: string]: any;
}

只讀的約束存在于第一次給對象賦值的時候,而不是第一次給只讀屬性賦值的時候

let person: Person = {
  id: 100,
  name: 'tom',
}
person05.id = 90;
// => 編譯報錯:id為只讀, 不可修改

let person2: Person = {
  name: 'welson',
  age: 2
}
// => 編譯報錯:給對象 person2 賦值,未定義只讀屬性id
person2.id = 1;
// => 編譯報錯:id為只讀, 不可修改

函數(shù)類型接口

// 只有參數(shù)列表和返回值類型的函數(shù)定義, 參數(shù)列表里的每個參數(shù)都需要名字和類型
interface SearchFunc {
  (source: string, subString: string): boolean;
}

函數(shù)

函數(shù)聲明

function sum(x: number, y: number): number {
    return x + y;
}

輸入多余的(或者少于要求的)參數(shù),是不被允許的

sum(1, 2, 3);
// 編譯報錯:多了1個參數(shù)

匿名函數(shù)(函數(shù)表達式)

let mySum = function (x: number, y: number): number {
    return x + y;
};

上面的代碼只對等號右側(cè)的匿名函數(shù)進行了類型定義,而等號左邊的 mySum,是通過賦值操作進行類型推論而推斷出來的。如果需要我們手動給 mySum 添加類型,則應(yīng)該是這樣:

let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};
// 注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>

用接口定義函數(shù)的形狀

interface FuncAdd {
  (value: number, increment: number): number
}
let add: FuncAdd;
add = function(value: number, increment: number): number {
  return value + increment;
}
// 函數(shù)的參數(shù)名不需要與接口里定義的名字相匹配
let add2: FuncAdd;
add2 = function(a: number, b: number) {
  return a + b;
}

可選參數(shù)

可選參數(shù)必須接在必需參數(shù)后面,換句話說,可選參數(shù)后面不允許再出現(xiàn)必須參數(shù)了

function addNum(a: number, b: number, c? :number): number {
    if(c) {
        return a + b + c;
    } else {
        return a + b;
    }
}
console.log(add(1, 2));

默認參數(shù)

類比 ES6 中的默認值

function add(a: number = 1, b: number): number {
    return a + b;
}
console.log(add(undefined, 1));

剩余參數(shù)

類比 Es6 中對象展開

interface AddFunc {
  (num1: number, ...rest: number[]): number
}
let add: AddFunc;
add = function(a: number, ...rest: number[]): number {
    let result = a; 
    rest.map(v => result += v);
    return result;
}
console.log(add(1,2,3,4));

函數(shù)重載

重載是為同一個函數(shù)提供多個函數(shù)類型定義,允許函數(shù)對傳入不同的參數(shù)返回不同的的結(jié)果分別做類型檢查

比如實現(xiàn)一個數(shù)字或字符串的反轉(zhuǎn)函數(shù):

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

上述函數(shù)利用聯(lián)合類型實現(xiàn),但有一個缺點,無法精確檢查輸入和輸出類型,即輸入數(shù)字輸出也應(yīng)該為數(shù)字,這時就可以使用重載定義多個函數(shù)類型:

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

重復定義多次函數(shù) reverse,前幾次都是函數(shù)定義,最后一次是函數(shù)實現(xiàn)。

TypeScript與JavaScript的處理流程相似,它會查找重載列表,從第一個重載定義開始匹配,如果匹配的話就使用這個定義,所以多個函數(shù)定義如果有包含關(guān)系,需要優(yōu)先把精確的定義寫在前面。

類 class

同ES6 的 class

相關(guān)概念

  • 類(Class):定義了一件事物的抽象特點,包含它的屬性和方法
  • 對象(Object):類的實例,通過 new 生成
  • 面向?qū)ο螅∣OP)的三大特性:封裝、繼承、多態(tài)
  • 封裝(Encapsulation):將對數(shù)據(jù)的操作細節(jié)隱藏起來,只暴露對外的接口。外界調(diào)用端不需要(也不可能)知道細節(jié),就能通過對外提供的接口來訪問該對象,同時也保證了外界無法任意更改對象內(nèi)部的數(shù)據(jù)
  • 繼承(Inheritance):子類繼承父類,子類除了擁有父類的所有特性外,還有一些更具體的特性
  • 多態(tài)(Polymorphism):由繼承而產(chǎn)生了相關(guān)的不同的類,對同一個方法可以有不同的響應(yīng)。比如 CatDog 都繼承自 Animal,但是分別實現(xiàn)了自己的 eat 方法。此時針對某一個實例,我們無需了解它是 Cat 還是 Dog,就可以直接調(diào)用 eat 方法,程序會自動判斷出來應(yīng)該如何執(zhí)行 eat
  • 存取器(getter & setter):用以改變屬性的讀取和賦值行為
  • 修飾符(Modifiers):修飾符是一些關(guān)鍵字,用于限定成員或類型的性質(zhì)。比如 public 表示公有屬性或方法
  • 抽象類(Abstract Class):抽象類是供其他類繼承的基類,抽象類不允許被實例化。抽象類中的抽象方法必須在子類中被實現(xiàn)
  • 接口(Interfaces):不同類之間公有的屬性或方法,可以抽象成一個接口。接口可以被類實現(xiàn)(implements)。一個類只能繼承自另一個類,但是可以實現(xiàn)多個接口

類的定義

使用 class 定義類,使用 constructor 定義構(gòu)造函數(shù)。

通過 new 生成新實例的時候,會自動調(diào)用構(gòu)造函數(shù)。

class Animal {
        name:string; // 定義屬性
    constructor(name) {
        this.name = name; // 屬性賦值
    }
    sayHi() {
        return `我叫 ${this.name}`;
    }
}

let cat = new Animal('Tom');
console.log(cat.sayHi()); // 我叫 Tom

類的繼承

使用 extends 關(guān)鍵字實現(xiàn)繼承,子類中使用 super 關(guān)鍵字來調(diào)用父類的構(gòu)造函數(shù)和方法。

class Cat extends Animal {
    color: string;
    constructor(name, color) {
        super(name); // 調(diào)用父類Animal的 constructor(name)
        this.color = color
    }
    sayHi() {
        // 調(diào)用父類的 sayHi();
        return super.sayHi() + '我是一只'+ this.color + ' 色的貓,'; 
    }
}

let c = new Cat('Tom', '橘黃'); // Tom
console.log(c.sayHi()); // 我叫 Tom,我是一只橘黃色的貓;

let cat2 = new Cat('Jerry');
cat2.color = '黑';
console.log(c.sayHi()); // 我叫 Jerry,我是一只黑色的貓;

存取器

使用 getter 和 setter 可以改變屬性的賦值和讀取行為:

class Animal {
        name:string;
    constructor(name) {
        this.name = name;
    }
    get name() {
        return 'Jack';
    }
    set name(value) {
        console.log('setter: ' + value);
    }
}

let a = new Animal('Kitty'); // setter: Kitty
a.name = 'Tom'; // setter: Tom
console.log(a.name); // Jack

實例屬性和方法

js中的屬性和方法:

// js中
function Person(name) {
  this.name = name; // 實例屬性
  this.eat = function(){ console.log('eat') };  // 實例方法
}
Person.age = 19; // 靜態(tài)屬性
Person.sleep = function(){ console.log('sleep') }; // 靜態(tài)方法

// 訪問實例方法和屬性:
var tom = new Person('tom');
console.log(tom.name) // tom
tom.eat();
tom.sleep() // error: tom.sleep is not a function

// 訪問靜態(tài)方法和屬性:
console.log(Person.age); // 19
Person.sleep();
Person.eat(); // error: Person.eat is not a function

ES6 中實例的屬性只能通過構(gòu)造函數(shù)中的 this.xxx 來定義:

class Animal {
    constructor(){
            this.name = 'tom';
        }
    eat() {}
}

let a = new Animal();
console.log(a.name); // tom

ES7 提案中可以直接在類里面定義:

// ts
class Animal {
    name = 'tom';
    eat() {}
}

let a = new Animal();
console.log(a.name); // Jack

靜態(tài)屬性和方法

ES7 提案中,可以使用 static 定義一個靜態(tài)屬性或方法。靜態(tài)方法不需要實例化,而是直接通過類來調(diào)用:

// ts
class Animal {
    static num = 42;
    static isAnimal(a) {
        return a instanceof Animal;
    }
}

console.log(Animal.num); // 42
let a = new Animal('Jack');
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function

訪問修飾符

public

公有屬性或方法,可以在任何地方被訪問到,默認所有的屬性和方法都是 public

private

私有屬性或方法,不能在聲明它的類的外部訪問,也不可以在子類中訪問

protected

受保護的屬性或方法,它和 private 類似,區(qū)別是它可以在子類中訪問

class Person {
    public name:string;
    private idCard:number;
    protected phone:number;
    constructor(name,idCard,phone) {
        this.name = name;
        this.idCard = idCard;
        this.phone = phone;
    }
}

let tom = new Person('tom',420000,13811110000);
console.log(tom.name) // tom

console.log(tom.idCard) 
// error:Property 'idCard' is private and only accessible within class 'Person'.

console.log(tom.phone)
// error:Property 'phone' is protected and only accessible within class 'Person' and its subclasses

class Teacher extends Person {
    constructor(name,idCard,phone) {
        super(name,idCard,phone);
        console.log(this.name)
        console.log(this.phone)
                console.log(this.idCard)
                // error:Property 'idCard' is private and only accessible within class 'Person'.
    }
}

多態(tài)

同一個父類的多個子類,可以有不同結(jié)果的同名方法:

class Person {
  eat(){ console.log('eat') }
}
class A extends Person {
  eat(){ console.log('A eat') }
}
class B extends Person {
  eat(){ console.log('B eat') }
}

抽象類/抽象方法 abstract

abstract 用于定義抽象類和其中的抽象方法。

  1. 抽象類是提供給其他類繼承的基類(父類),是不允許被實例化
  2. 抽象方法只能包含在抽象類中
  3. 子類繼承抽象類,必須實現(xiàn)抽象類中的抽象方法
abstract class Animal {
    abstract eat(); // 抽象方法
    // 普通方法
    sleep(){
      console.log('sleep')
    }
}

let a = new Animal(); // 報錯,抽象類不能被實例化

class Cat extends Animal {
    eat(){ 
        // 父類的eat方法必須被實現(xiàn)
      console.log('eat')
    }
}

類與接口

前面介紹了 接口 可以用來描述 對象(object)的形狀,這一章主要介紹 接口類(class)的行為 進行抽象。

類實現(xiàn)接口 implements

實現(xiàn)(implements)是面向?qū)ο笾械囊粋€重要概念。一個類只能繼承自另一個類,不同類之間可能會有一些共有特性,提取多個類的共有特性,作為一個接口,再用 implements 關(guān)鍵字來實現(xiàn)就可以大大提高面向?qū)ο蟮撵`活性。

舉例: 人是一個類,人需要吃東西。動物是一個類,動物也需要吃東西。這種情況就可以把 吃東西 提取出來作為一個接口:

interface Ieat {
   eat();
}

class Person implements Ieat{
  eat(){}
}

class Animal implements Ieat {
  eat(){}
}

一個類也可以實現(xiàn)多個接口:

interface Ieat {
   eat();
}

interface Isleep {
    sleep();
}

class Person implements Ieat, Isleep{
  eat(){}
  sleep() {}
}

接口繼承接口

interface Alarm {
    alert();
}

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

接口繼承類

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

interface Point3d extends Point {
    z: number;
}

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

混合類型

前面介紹了接口可以用來定義函數(shù)的形狀,有時候,一個函數(shù)還可以有自己的屬性和方法:

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;

泛型

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

打印number:

function printer(arr:number[]):void {
  for(var item of arr) {
    console.log(item)
  }
}
printer([1,2,3,4])

打印字符串:

// 打印字符串
function printer1(arr:string[]):void {
  for(var item of arr) {
    console.log(item)
  }
}
printer1(['a','b','c','d'])

使用 any 也可以通過編譯,但是無法準確定義返回值的類型,這個時候就可以使用泛型函數(shù)

泛型函數(shù)

在函數(shù)名后加上 <T> (也可以是其他別的字母),其中 T 用來指代輸入的類型,在函數(shù)內(nèi)部就可以使用這個 T 類型。

function printer<T>(arr:T[]):void {
  for(var item of arr) {
    console.log(item)
  }
}
// 指定具體類型調(diào)用
printer<string>(['a','b','c','d']);
// 調(diào)用時也可以直接讓ts自己做類型推論
printer([1,2,3,4]);

也可以同時使用多個類型參數(shù)

function swap<S,P>(tuple:[S,P]):[P,S] {
  return [tuple[1], tuple[0]]
}
swap<string, number>(['a', 2])

泛型類

class arrayList<T> {
  name: T;
  list: T[] = [];
  add(val:T):void {
    this.list.push(val)
  }
}

var arr = new arrayList<number>();
arr.add(1)
arr.add(2)
console.log(arr.list)

泛型接口

interface Iadd<T> {
  (x:T,y:T):T;
}

var add:Tadd<number> = function(x:number,y:number):number {
  return x + y
}

泛型約束

在函數(shù)內(nèi)部使用泛型變量的時候,由于事先不知道它是哪種類型,所以不能隨意的操作它的屬性或方法

獲取一個參數(shù)的長度:

function getLength<T>(arg:T):T {
    console.log(arg.length) // error: Property 'length' does not exist on type 'T'
  return arg;
}

上例中,泛型 T 不一定包含屬性 length,所以編譯的時候報錯了,這時候就可以使用泛型約束,使用 extends 約束泛型 <T> 必須符合 Ilength 的形狀,也就是必須包含 length 屬性:

interface Ilength {
  length: number
}

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

getLength('abcd') // 4

getLength(7) // error: Argument of type '7' is not assignable to parameter of type 'Ilength'.

多個參數(shù)間也可以互相約束:

function copyFields<T extends U, U>(target: T, source: U): T {
    for (let id in source) {
        target[id] = (<T>source)[id]; 
    }
    return target;
}

let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 })

上例中,使用了兩個類型參數(shù),其中要求 T 繼承 U,這樣就保證了 U 上不會出現(xiàn) T 中不存在的字段。

聲明文件 declare

當使用第三方庫時,我們需要引用它的聲明文件,才能獲得對應(yīng)的代碼補全、接口提示等功能。

聲明語句

假如我們使用第三方庫 jQuery,來獲取一個元素

$('#foo');
jQuery('#foo');

但是在 ts 中,編譯器并不知道 $jQuery 是什么東西:

jQuery('#foo');
// ERROR: Cannot find name 'jQuery'.

這時,我們需要使用 declare var 來定義它的類型,declare var 并沒有真的定義一個變量,只是定義了全局變量 jQuery 的類型,僅僅會用于編譯時的檢查,在編譯結(jié)果中會被刪除。

declare var jQuery: (selector: string) => any;
jQuery('#foo');

聲明文件

通常我們會把聲明語句放到一個單獨的文件(xxx.d.ts)中,這就是聲明文件,聲明文件必需以 .d.ts 為后綴。

一般來說,ts 會解析項目中所有的 *.ts 文件,當然也包含以 .d.ts 結(jié)尾的文件。所以當我們將 jQuery.d.ts 放到項目中時,其他所有 *.ts 文件就都可以獲得 jQuery 的類型定義了。

這是使用全局變量模式的聲明文件,還有其他模式如 模塊導入 等會在后面介紹。

第三方聲明文件

社區(qū)已經(jīng)幫我們定義好了很多第三方庫的聲明文件,可以直接下載下來使用,更推薦使用 @types 統(tǒng)一管理第三方庫的聲明文件。@types 的使用方式很簡單,直接用 npm 安裝對應(yīng)的聲明模塊即可,以 jQuery 舉例:

npm install @types/jquery --save-dev

可以在這個頁面搜索你需要的聲明文件。

書寫聲明文件

當一個第三方庫沒有提供聲明文件時,我們就需要自己書寫聲明文件了。

在不同的場景下,聲明文件的內(nèi)容和使用方式會有所區(qū)別:

  • 全局變量:通過 <script> 標簽引入第三方庫,注入全局變量
  • npm 包:通過 import foo from 'foo' 導入,符合 ES6 模塊規(guī)范
  • UMD 庫:既可以通過 <script> 標簽引入,又可以通過 import 導入
  • 模塊插件:通過 import 導入后,可以改變另一個模塊的結(jié)構(gòu)
  • 直接擴展全局變量:通過 <script> 標簽引入后,改變一個全局變量的結(jié)構(gòu)。比如為 String.prototype 新增了一個方法
  • 通過導入擴展全局變量:通過 import 導入后,可以改變一個全局變量的結(jié)構(gòu)

全局變量

未完待續(xù) 。。。

TypeScript && React

個人倉庫: 用 TypeScript 編寫 React 的 demo 示例

感謝

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372