TS系列之接口/类/泛型

接口

接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动须要由类(classes)去实现(implements)。
命名通常使用大驼峰法。express

interface Person {
    name: string;
    age: number;
}

let tom: Person = {
    name: 'Tom',
    age: 25
};

这样咱们就约束了 tom 的形状必须和接口 Person 一致。
函数类型app

interface SearchFunc {
  (source: string, subString: string): boolean;
}
//对于函数类型的类型检查来讲,函数的参数名不须要与接口里定义的名字相匹配。 
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
  let result = src.search(sub);
  return result > -1;
}

属性

可选属性

interface Person {
    name: string;
    age?: number;
}

let tom: Person = {
    name: 'Tom'
};

可索引属性

共有支持两种索引签名:字符串和数字。 能够同时使用两种类型的索引,可是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是由于当使用 number来索引时,JavaScript会将它转换成string而后再去索引对象。ide

// string
interface Person {
    name: string;
    age?: number;
    [propName: string]: any;       //[propName: string] 定义了任意属性取 string 类型的值。
}

let tom: Person = {
    name: 'Tom',
    gender: 'male'
};
// number
interface StringArray {
  [index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];
一旦定义了任意属性,那么肯定属性和可选属性都必须是它的子属性

只读属性

interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}
//报错
let tom: Person = {
    id: 89757,
    name: 'Tom',
    gender: 'male'
};

tom.id = 9527;
//报错
let tom: Person = {
    name: 'Tom',
    gender: 'male'
};

tom.id = 89757;
只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候

继承

接口继承接口

和类同样,接口也能够相互继承。 这让咱们可以从一个接口里复制成员到另外一个接口里,能够更灵活地将接口分割到可重用的模块里。函数

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
一个接口能够继承多个接口,建立出多个接口的合成接口。

接口继承类

当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了全部类中存在的成员,但并无提供具体实现同样。 接口一样会继承到类的private和protected成员。 这意味着当你建立了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。ui

class Control {
    private state: any;
}

interface SelectableControl extends Control {
    select(): void;
}

class Button extends Control implements SelectableControl {
    select() { }
}

class TextBox extends Control {
    select() { }
}

// 错误:“Image”类型缺乏“state”属性。
class Image implements SelectableControl {
    select() { }
}

在上面的例子里,SelectableControl包含了Control的全部成员,包括私有成员state。 由于 state是私有成员,因此只可以是Control的子类们才能实现SelectableControl接口。this

属性和方法

使用 class 定义类,使用 constructor 定义构造函数。
经过 new 生成新实例的时候,会自动调用构造函数。code

class Animal {
    constructor(name) {
        this.name = name;
    }
    sayHi() {
        return `My name is ${this.name}`;
    }
    eat() {
        return 'happy';
    }
}

let a = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack

继承

使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法。对象

class Cat extends Animal {
    constructor(name) {
        super(name);      // 调用父类的 constructor(name)
        console.log(this.name);
    }
    // 方法重写
    sayHi() {             
        return 'Meow, ' + super.sayHi();    // 调用父类的 sayHi()
    }
}

let c = new Cat('Tom');   // Tom
c.sayHi();                // Meow, My name is Tom
c.eat();                  //'happy'
父类包含了一个构造函数,它必须调用 super(),它会执行子类的构造函数。 并且,在构造函数里访问 this的属性以前,咱们必定要调用 super()。 这个是TypeScript强制执行的一条重要规则。

存取器

使用 getter 和 setter 能够改变属性的赋值和读取行为继承

class Animal {
    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

类的类型

let a: Animal = new Animal('Jack');

修饰符

static

静态属性索引

class Animal {
    static num = 42;

    constructor() {
        // ...
    }
}
console.log(Animal.num); // 42

静态方法
使用 static 修饰符修饰的方法称为静态方法,它们不须要实例化,而是直接经过类来调用

class Animal {
    static isAnimal(a) {
        return a instanceof Animal;
    }
}

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

public

public 修饰的属性或方法是公有的,能够在任何地方被访问到,默认全部的属性和方法都是 public 的

class Animal {
    public name;
    public constructor(name) {
        this.name = name;
    }
}

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

protected

protected 修饰的属性或方法是受保护的,它和 private 相似,区别是它在子类中也是容许被访问的

class Animal {
    protected name;
    public constructor(name) {
        this.name = name;
    }
}

class Cat extends Animal {
    constructor(name) {
        super(name);
        console.log(this.name);
    }
}

private

private 修饰的属性或方法是私有的,不能在声明它的类的外部访问

class Animal {
    private name;
    public constructor(name) {
        this.name = name;
    }
}

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

使用 private 修饰的属性或方法,在子类中也是不容许访问的

class Animal {
    private name;
    public constructor(name) {
        this.name = name;
    }
}

class Cat extends Animal {
    constructor(name) {
        super(name);
        console.log(this.name);
    }
}         
//Error

readonly

使用 readonly关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。

class Octopus {
    readonly name: string;
    readonly numberOfLegs: number = 8;
    constructor (theName: string) {
        this.name = theName;
    }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的.

参数属性

参数属性能够方便地让咱们在一个地方定义并初始化一个成员。

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

抽象类

abstract 用于定义抽象类和其中的抽象方法。

  • 抽象类是不容许被实例化的
  • 抽象类中的抽象方法必须被子类实现
abstract class Animal {
    public name;
    public constructor(name) {
        this.name = name;
    }
    public abstract sayHi();
}

let a = new Animal('Jack');
//Error

抽象类中的抽象方法不包含具体实现而且必须在派生类中实现。 抽象方法的语法与接口方法类似。

abstract class Animal {
    public name;
    public constructor(name) {
        this.name = name;
    }
    public abstract sayHi();
}

class Cat extends Animal {
    public sayHi() {
        console.log(`Meow, My name is ${this.name}`);
    }
}

let cat = new Cat('Tom');

装饰器

装饰器是一种特殊类型的声明,它可以被附加到类声明,方法, 访问符,属性或参数上。 装饰器使用 @expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息作为参数传入。
多个装饰器能够同时应用到一个声明上,就像下面的示例:

书写在同一行上:
@f @g x

书写在多行上:
@f
@g
x

在TypeScript里,当多个装饰器应用在一个声明上时会进行以下步骤的操做:

  1. 由上至下依次对装饰器表达式求值。
  2. 求值的结果会被看成函数,由下至上依次调用。

装饰器求值

类中不一样声明上的装饰器将按如下规定的顺序应用:

  1. 参数装饰器,而后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每一个实例成员。
  2. 参数装饰器,而后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每一个静态成员。
  3. 参数装饰器应用到构造函数。
  4. 类装饰器应用到类。

类实现接口

实现(implements)是面向对象中的一个重要概念。通常来说,一个类只能继承自另外一个类,有时候不一样类之间能够有一些共有的特性,这时候就能够把特性提取成接口(interfaces),用 implements 关键字来实现。这个特性大大提升了面向对象的灵活性。

举例来讲,门是一个类,防盗门是门的子类。若是防盗门有一个报警器的功能,咱们能够简单的给防盗门添加一个报警方法。这时候若是有另外一个类,车,也有报警器的功能,就能够考虑把报警器提取出来,做为一个接口,防盗门和车都去实现它:

interface Alarm {
    alert();
}

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

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

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

泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

//泛型函数的类型与非泛型函数的类型没什么不一样,只是有一个类型参数在最前面,像函数声明同样
function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray<string>(3, 'x'); // ['x', 'x', 'x']

//泛型参数的默认类型
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;
}

泛型接口

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

let createArray: CreateArrayFunc;
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']

//也能够把泛型参数提早到接口名上
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; };

泛型约束

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}
//对泛型进行约束,只容许这个函数传入那些包含 length 属性的变量

咱们定义一个接口来描述约束条件。 建立一个包含 .length属性的接口,使用这个接口和extends关键字来实现约束。

相关文章
相关标签/搜索