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关键字声明抽象类和抽象方法,子类继承抽象类后,须要实现抽象方法。一样的,抽象类不能被实例化。

相关文章
相关标签/搜索