typescript入门总结

119次阅读
没有评论

ts 的前世今生

  • ts 的全称是 typescript,它是 javascript 的超集。
  • ts 补充了 javascript 作为弱类型脚本语言的弱点,即增加了静态类型标注。
  • 同时,ts 带来了某些 ECMAScript 提案中的特性与语法。
  • ts 编译之后生成的是纯净的 JavaScript。

安装:

npm i -g typescript
tsc -v

编译 ts 文件

tsc xxx.ts  

注解

let name: string = 'Tom Hanse'
let isTrue: boolean = true
let number: number = 5
function(color: string){
    console.log(color)
}

基础类型

布尔类型

let flag: boolean = true

数值

let num: number = 1

字符串

let name: string = 'me, string!'

空与未定义

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

null 和 undefined 类型是所有类型的子类型,因此,可以将任何类型赋值为 null 或 undefined

let n: number = 4
n = undefined
let u:undefined
let m = u

空值

void 用来表示没有任何返回值的函数

function reuturnNothing(): void{
    let str: string = 'this function return nothing'
    alert(str)
}

任意值

any 用来代表任意类型,可以访问任意属性和方法,而且任何操作的返回值都是任意类型

let anything: any = 'Tom'
console.log(anything.myname)
console.log(anything.myname())

如果一个变量在声明时未指定类型,且未赋值,则默认为 任意类型

数组

typescript 中定义数组有两种方式

let arr: number[] = [1,2,3]
let bookList: Array<string> = ['三体','三国演义']

元组

元组类型表示一个已知元素数量和类型的数组,各元素的类型不必相同

let arr: [string, number, boolean]
arr = ['i love you', 3000, true]

如果访问元素越界了,则会使用联合类型进行替代,接着上面的代码

arr[3] = 'times'
arr[4] = false

Enum

枚举类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等

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

枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射,这可以用于反向查找:

console.log(Days["Sun"] === 0); // true
console.log(Days[0] === "Sun"); // true

也可以给枚举项手动赋值

enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 7); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

never

表示的是那些永不存在的值的类型,如那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
  throw new Error(message)
}

// 推断的返回值类型为never
function fail() {
  return error("Something failed")
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
  while (true) {}
}

object

表示非原始类型,也就是除 number,string,boolean,symbol,null 或 undefined 之外的类型

declare 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

类型断言

假如你非常了解某个变量的具体信息,那么你可以通过类型断言来告诉 typescript,断言有两种方法,在tsx中必须使用第二种

let sth: any = 'this are sth special here'
let sthLength: number = (<string>sth).length
let sthLength2: number = (sth as string).length

类型推断

如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查
如果定义的时候进行了赋值,那么就会推断为值的类型

let myFavoriteNumber = 'seven';
myFavoriteNumber = 7; //error
let myFavoriteNumber;
myFavoriteNumber = 'seven'; //OK
myFavoriteNumber = 7; //ok

联合类型

表示取值可以为多种类型中的一种

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法

function getLength(something: string | number): number {
    return something.length; //error
}
function getString(something: string | number): string {
    return something.toString(); //ok
}

联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length); // 5
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); //error

接口

interface 可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。

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

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

接口一般首字母大写,并且赋值的时候,变量的形状必须和接口的形状保持一致

可选的属性与任意的属性:假如我们的接口与变量的形状有差别,那么可以利用可选属性或者任意属性来描述
需要注意:如果定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

interface Person {
    name: string;
    age?: string; //可选的属性
    [propName: string]: string; //任意的属性
}

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

interface Person {
    name: string;
    age?: number; //error 因为定义了任意属性,且任意属性为 string 类型
    [propName: string]: string; //任意的属性
}

只读属性:readonly 可以用来定义某个属性只读,而不能被再次赋值

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

let tom: Person = {
    id: 89757,
    name: 'Tom',
    gender: 'male'
};

tom.id = 1 //error

接口继承:接口也可以相互继承

interface Shape {
  color: string
}

interface Square extends Shape {
  sideLength: number
}

interface PenStroke extends Shape, Square{
  penWidth: number
}

let square = {} as Square
square.color = 'blue'
square.sideLength = 10

类数组

类数组不应该用普通数组的方式来定义,而应该用接口
类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等

function sum() {
    let args: IArguments = arguments;
}

内置对象

在 TypeScript 中将变量定义为内置对象类型:

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 提供的内置对象有:Document、HTMLElement、Event、NodeList 等

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

函数

一个函数有输入和输出,要在 TypeScript 中对其进行约束,需要把输入和输出都考虑到
函数声明的写法与函数表达式的写法略有区别,因为表达式还需要对声明的函数变量进行描述

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

注意上面函数表达式中的 => 与ES6 中的箭头函数是不同的,这里的 => 的左边表示函数的输入的约束,右边表示函数的输出的约束

我们也可以用接口对函数进行约束,这样看起来可能比表达式更好理解

interface SearchFunc {
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    return source.search(subString) !== -1;
}

可选参数:在函数中也使用 ? 来描述可选的参数,其中可选的参数要放在必需参数的后面

function buildName(firstName: string, lastName?: string) {
    if (lastName) {
        return firstName + ' ' + lastName;
    } else {
        return firstName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');

参数默认值:TypeScript 会将添加了默认值的参数识别为可选参数,这时就不受「可选参数必须接在必需参数后面」的限制了

function buildName(firstName: string = 'Tom', lastName: string) {
    return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');

参数的展开与解构

function push(array: any[], ...items: any[]) {
    items.forEach(function(item) {
        array.push(item);
    });
}

let a = [];
push(a, 1, 2, 3);

函数重载:javascript 中函数不存在重载的概念,我们可以通过对参数的判断实现伪重载

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

上面的代码中,我们重复定义了多次函数reverse,前几次都是函数定义,最后一次是函数实现。
TypeScript会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面

接口与函数的混合

interface Counter {
  (start: number): string
  interval: number
  reset(): void
}

function getCounter(): Counter {
  let counter = (function (start: number) { }) as Counter
  counter.interval = 123
  counter.reset = function () { }
  return counter
}

let c = getCounter()
c(10)
c.reset()
c.interval = 5.0

public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的

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

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

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

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

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

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

当构造函数修饰为 private 时,该类不允许被继承或者实例化

class Animal {
    public name: string;
    private constructor (name: string) {
        this.name = name;
  }
}
class Cat extends Animal { //error
    constructor (name) {
        super(name);
    }
}

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

当构造函数修饰为 protected 时,该类只允许被继承

class Animal {
    public name: string;
    protected constructor (name: string) {
        this.name = name;
  }
}
class Cat extends Animal {
    constructor (name) {
        super(name);
    }
}

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

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

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

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

static 是ES7中的一个提案,用来定义静态的属性,这些属性存在于类本身上面而不是类的实例上

class Animal {
    static num: number = 42;

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

readonly 只读属性,必须在声明时或构造函数里被初始化

class Person {
    readonly age: number
  constructor(public readonly name: string, age: number) {
    this.name = name
    this.age = age
  }
}
let a = new Animal('Jack',25);
console.log(a.name); // Jack
a.name = 'Tom'; //error
a.age = 26; //error

abstract 用于定义抽象类和其中的抽象方法,首先,抽象类是不允许被实例化,其次,抽象类中的抽象方法必须被子类实现

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

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

class Cat extends Animal {
    public sayHi(): void {    //继承类必须实现这个方法
        console.log(`Meow, My name is ${this.name}`);
    }
    public eat(): void {
        console.log(`${this.name} is eating.`);
    }
}

let cat = new Cat('Tom');

存取器:使用 getter 和 setter 可以改变属性的赋值和读取行为

// 先检查用户密码是否正确,然后再允许其修改员工信息
class Employee {
  private _fullName: string
  private _passcode: string

  constructor(readonly passcode: string){
    this._passcode = passcode
  }
  get fullName(): string {
    return this._fullName
  }

  set fullName(newName: string) {
    if (this._passcode && this._passcode == 'secret passcode') {
      this._fullName = newName
    }
    else {
      console.log('Error: Unauthorized update of employee!')
    }
  }
}

let employee = new Employee('secret passcode')
employee.fullName = 'Bob Smith'
if (employee.fullName) {
  console.log(employee.fullName)
}

允许使用接口的地方也允许使用类

class Point {
  x: number
  y: number
}

interface Point3d extends Point {
  z: number
}

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

类与接口

interface 不仅可以用来对 class 进行属性描述,而且也可以实现 class 的方法,这叫做 类实现接口。

假如不同类之间有一些共有的特性,就可以把特性提取成 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');
    }
}

接口也可以继承类

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

interface Point3d extends Point {
    z: number;
}

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