Typescript 入门基础篇(二)

686 阅读6分钟

Typescript 入门基础篇(二)

Typescript 入门基础篇(一)传送门。
Typescript 入门基础篇(三)传送门。

上一篇说到了存储器

抽象类

抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。抽象方法的语法与接口方法相似。 两者都是定义方法签名但不包含方法体。 然而,抽象方法必须包含abstract关键字并且可以包含访问修饰符。

abstract class Department {

    constructor(public name: string) {
    }

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

    abstract printMeeting(): void; // 必须在派生类中实现
}

class AccountingDepartment extends Department {

    constructor() {
        super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super()
    }

    // 派生类中必须实现抽象基类中的抽象方法
    printMeeting(): void {
        console.log('The Accounting Department meets each Monday at 10am.');
    }

    generateReports(): void {
        console.log('Generating accounting reports...');
    }
}
let department: Department; // 允许创建一个对抽象类型的引用
department = new Department(); // 错误: 不能创建一个抽象类的实例
department = new AccountingDepartment(); // 允许对一个抽象子类进行实例化和赋值
department.printName();
department.printMeeting();
department.generateReports(); // 错误: 方法在声明的抽象类中不存在

高级技巧

  • 构造函数 在Typescript中声明一个类的时候,实际上声明了很多东西,首先是类的实例的类型。构造函数的类型包含了类的所有静态属性。换个角度说,我们可以认为类具有实例部分和静态部分这两个部分。
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; // typeof Greeter 意思为取Greeter类的类型。而不是实例的类型。或者更确切的说,‘告诉我Greeter标识符的类型’,也就是构造函数的类型。
greeterMaker.standardGreeting = "Hey there!";

let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());
  • 把类当做接口使用 类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类。
class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};

函数

这里依然只列出与JS不一样的地方

# 完整的函数类型书写
function add (x : number, y : number) : number {
    return x + y;
}
let orgAdd : ( x : number, y : number ) => number = function ( x : number, y : number ) : number { return x + y; }
参数类型的名字不必与参数名相同,只要类型是匹配的就好了,就认为它是有效的参数类型。

# 推断类型(按上下文归类)
赋值语句一边指定了类型,另一边没有指定类型,这个时候typescript编译器会自动识别出来。

# 可选参数
function arguments( fir : string, sec ?: string ) : string {
    if(sec)
    return fir + '' + sec;
    else
    return fir;
}
区别js和ts的可选参数
js中的参数都是可选的,不传的时候则为undefined,在ts里在参数旁边使用?可以实现可选参数的功能。
可选参数必须跟在必须参数后面

# 剩余参数
js中如果操作多个参数可以使用arguments,而ts中你可以把所有参数收集到一个变量里。
function rest ( fir : string, ...rest : string[] ) : string {
    return fir + ' ' + rest.join(' ');
}

函数的类型只是由参数类型和返回值组成的。 函数中使用的捕获变量不会体现在类型里。 实际上,这些变量是函数的隐藏状态并不是组成API的一部分。

this

这里单独把this拿出来说,因为比较重要。如果你还不懂js里的this,那么先跳转传送门Understanding JavaScript Function Invocation and "this";

# 提供显式的`this`
interface Card {
    suit: string;
    card: number;
}
interface Deck {
    suits: string[];
    cards: number[];
    createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    // NOTE: The function now explicitly specifies that its callee must be of type Deck
    createCardPicker: function(this: Deck) {
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);
现在TypeScript知道createCardPicker期望在某个Deck对象上调用。 也就是说 this是Deck类型的,而非any,因此--noImplicitThis不会报错了。

# 重载
let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
    // Check to see if we're working with an object/array
    // if so, they gave us the deck and we'll pick the card
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // Otherwise just let them pick the card
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
为了让编译器能够选择正确的检查类型,它与JavaScript里的处理流程相似。 它查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。

泛型

# 举个栗子
function identity<T>(arg: T): T {
    return arg;
}
这里的T为类型变量,表示类型而不是值。
这里T帮助ts捕捉用户输入的类型,之后就可以使用这个类型。使用T当做返回值类型,这样可以知道传入值和返回值类型是相同的。这样就可以跟踪函数里使用的类型的信息。这个就叫做泛型。可以适用于多个类型,不会丢失信息。保持准确性,传入数值类型并返回数值类型。

# 使用泛型函数
1. 传入所有参数,包含类型参数
let output = identity<string>('mystring');
2. 利用类型推论 -- 即编译器会根据传入的参数自动的帮助我们确定T的类型
let output = identity('mystring');
类型推论帮助我们保持代码精简和高可读性。如果编译器不能够自动地推断出类型的话,只能像上面那样明确的传入T的类型,在一些复杂的情况下,这是可能出现的。

# 泛型接口
interface genFn<T> {
    ( arg : T ) : T;
};
let ide : genFn<number> = identity;

# 泛型类
class Gen <T> {
    zero : T;
    add : ( x : T, y : T ) => T;
}
let gen = new Gen<Number>();
泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。

# 泛型约束
利用接口来描述约束条件,然后使用这个接口和extends关键字实现约束
interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}
这时的泛型函数被定义了约束,因此它不再是适用于任何类型。

# 泛型约束中使用类型参数
function getProperty(obj: T, key: K) {
    return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.

# 在泛型里使用类类型
class BeeKeeper {
    hasMask: boolean;
}

class ZooKeeper {
    nametag: string;
}

class Animal {
    numLegs: number;
}

class Bee extends Animal {
    keeper: BeeKeeper;
}

class Lion extends Animal {
    keeper: ZooKeeper;
}

function createInstance<A extends Animal>(c: new () => A): A {
    return new c();
}

createInstance(Lion).keeper.nametag;  // typechecks!
createInstance(Bee).keeper.hasMask;   // typechecks!
使用原型属性推断并约束构造函数与类实例的关系。

你也可以关注我的公众号