TypeScript

基础类型

string、number、boolean

array(数组)

ts 表示数组有两种形式,元素类型[] 或者 Array<元素类型>,示例:

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

如果是只读数组,被赋值后就不能修改了,使用ReadonlyArray<元素类型>

let rolist: ReadonlyArray<number> = [1, 2, 3, 4, 5];

unknown

any

enum(枚举)

数字枚举

默认就是数字枚举,从 0 开始递增,如果给某个成员赋值,后面的在这个值的基础上自增。

enum Direction {
  up,
  down,
  left,
  right,
} // up=0、down=1、left=2、right=3,从0开始依次递增

enum Direction {
  up,
  down = 2,
  left,
  right,
} // up=0、down=2、left=3、right=4,down后面的从2开始依次递增

字符串枚举

字符串枚举,因为不能自增,所以需要每个成员都进行初始化。

enum Direction {
  up = "up",
  down = "down",
  left = "left",
  right = "right",
}

异构枚举

混合字符串和数字,但是不推荐这么做。

enum BooleanLikeHeterogeneousEnum {
  No = 0,
  Yes = "YES",
}

外部枚举

void(空值)

null 和 undefined

当打印一个变量时,这个变量没有定义就是undefined,定义了没有赋值就是null

ts 中,nullundefined各自有自己的类型,分别是 null 和 undefined,它们本身作用不大,默认情况下,null 和 undefined 类型是所有其他类型的子类型,就是说你可以把nullundefined赋值给number类型的变量。

不过,指定–strictNullChecks,nullundefined就只能赋值给 any 和它们各自的类型(有一个例外是undefined还可以赋值给 void 类型)

never

void 表示没有任何类型,never 表示永远不存在的值的类型。

void 没有返回值,never 返回的值没有类型。

object

类型断言

类型转换,有两种形式:

// “尖括号”语法:
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;

在 TypeScript 里使用 JSX 时,只有 as 语法断言是被允许的。

关于 number、string、boolean、symbol、object

接口

接口的作用是规范、限制、约束。定义接口的关键字是 interface 和 type。

  • interface 可以重复定义(merge),type 不可以
  • interface 定义的接口格式固定,type 可以定义各种格式的接口
  • interface 可以实现接口的 extends/implements,type 不行,基于此,推荐约束变量类型的时候用 type,定义类接口的时候用 interface

属性类接口

interface LabeledValue {
  label: string;
}
function printLabel(labeledObj: LabeledValue) {
  console.log(labeledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);

定义接口的关键字 interface 或者 type,上例还可以写作:

type LabeledValue {
    label: string;
}

可选属性 ?

接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。

interface PaintOptions {
  shape: Shape;
  xPos?: number;
  yPos?: number;
}

function paintShape(opts: PaintOptions) {
  // ...
}

const shape = getShape();
paintShape({ shape });
paintShape({ shape, xPos: 100 });
paintShape({ shape, yPos: 100 });
paintShape({ shape, xPos: 100, yPos: 100 });

xPosyPos 均被视为可选

只读属性 readonly

一些对象属性在创建的时候定义其值,然后就再也不能被修改,这时候就可以在属性名前用 readonly 来指定只读属性:

// 定义接口
interface Point {
  readonly x: number;
  readonly y: number;
}

// p1和p2的x、y就再也不能被改变了
let p1: Point = { x: 1, y: 2 };
let p2: Point = { x: 3, y: 4 };

只读数组的类型是ReadonlyArray<元素类型>

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error! 可以看到ReadonlyArray不能赋值到普通数组,不过可以通过类型断言重写:a = ro as number[]
readonly 和 const

最简单判断该用 readonly 还是 const 的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用 const ,若做为属性则使用 readonly 。

额外的属性检查

示例一,正常:

interface LabeledValue {
  label: string;
}
function printLabel(labeledObj: LabeledValue) {
  console.log(labeledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);

示例二,报错:

interface LabeledValue {
  label: string;
}
function printLabel(labeledObj: LabeledValue) {
  console.log(labeledObj.label);
}
printLabel({ size: 10, label: "Size 10 Object" }); // error

prinLabel 函数期待的参数是 LabeledValue 类型的,所以 size 属性显然是多余的,示例一中,定义变量 myObject 时没有指定类型,这么写可以绕开检查,最后没有报错,此外还有两种方式也可以绕开检查:

// 使用类型断言
printLabel({ size: 10, label: "Size 10 Object" } as LabeledValue);
// 添加一个字符串索引签名
interface LabeledValue {
  label: string;
  [key: string]: any; // key是string类型,value是any类型
}
function printLabel(labeledObj: LabeledValue): void {
  console.log(labeledObj.label);
  console.log(labeledObl.size);
}
printLabel({ size: 100, label: "Size 10 Object" });

不提倡绕开检查,除非是包含方法和内部状态的复杂对象字面量,可能需要使用这些技巧。

函数类型接口

不常用

type Fn = {
  (a: string, b: number): void; // 返回类型为void,则表示不限制返回类型
};

let fn: Fn = (x, y) => {
  console.log(x);
  console.log(y);
};

fn("string", 2);

可索引接口

不常用,用于描述哪些能够”通过索引得到”的类型(数组和对象),比如 a[10]、b[“name”]

type A = {
  // 索引签名:index是索引,number是索引的类型,string是索引对应的值的类型
  [index: number]: string;
};

let a: A = ["string1", "string2"];
console.log(a);

类类型接口

interface X {
  name: string;
  eat(food: string): string; // 虽然定义了参数,但是其实并不限制参数,只限制返回类型
}

class A implements X {
  public name: string;
  constructor(name: string) {
    this.name = name;
  }
  eat() {
    console.log(123);
    return "food";
  }
}

let a = new A("lujinkai");
a.eat();

接口继承接口

interface A {
  name: string;
}

interface B extends A {
  age: number;
}

let b: B = { name: "lujinkai", age: 23 };

console.log(b);

接口继承类

接口不仅可以继承接口,还可以继承类。

示例:

class Control {
  private state: any;
}
interface SelectableControl extends Control {
  select(): void;
}

类静态部分和实例部分

ts 中,类分静态部分实例部分,new 实例化对象后,对象中有的部分就是实例化的部分,对象中没有的部分就是静态部分,构造函数 constructor 就是静态部分。

类类型接口只检查实例部分,不检查静态部分,如果想要检查静态部分,可以使用类表达式

示例:

interface ClockConstructor {
  new (hour: number, minute: number): void;
}
interface ClockInterface {
  tick(): void;
}
const Clock: ClockConstructor = class Clock implements ClockInterface {
  constructor(h: number, m: number) {}
  tick() {
    console.log("beep beep");
  }
};

函数

TypeScript 为 JavaScript 函数添加了额外的功能,让我们可以更容易地使用。

可选参数

function buildName(firstName: string, lastName?: string) {
  if (lastName) return firstName + " " + lastName;
  else return firstName;
}

默认参数

function buildName(firstName: string, lastName = "Smith") {
  return firstName + " " + lastName;
}

剩余参数

function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}
let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;

函数类型

// myAdd 是函数名
// (x:number, y:number) => number 是函数类型
// (x: number, y: number): number { return x + y; } 是函数
let myAdd:(x:number, y:number) => number = (x: number, y: number): number { return x + y; };

注意:不要混淆了 TypeScript 中的 => 和 ES6 中的 =>,在 TypeScript 的类型定义中,=> 用来表示函数的定义,=> 的左边是输入类型,需要用括号括起来,右边是输出类型

上例还可以写做:

type AddFunc =  (x:number, y:number) => number

let myAdd:AddFunc = (x: number, y: number): number { return x + y; };

或者:

interface AddFunc {
    (x:number, y:number):number
}

let myAdd:AddFunc = (x: number, y: number): number { return x + y; };

this

略。。。

重载

略。。。

字面量类型

js 基础复习

什么是构造函数

在 JavaScript 中,用 new 关键字来调用的函数,称为构造函数。构造函数首字母一般大写。

es6 的 class 类本质上是对构造函数的封装,示例:

// es6 class类
class Greeter {
  constructor(message) {
    this.greeting = message;
  }
  greet() {
    return "Hello, " + this.greeting;
  }
}
let greeter = new Greeter("world");
console.log(greeter.greet());

// 以上写法本质上和下面是一样的
function Greeter(message) {
  this.greeting = message;
}
Greeter.prototype.greet = function () {
  return "Hello, " + this.greeting;
};

let greeter = new Greeter("world"); // Greeter就是构造函数
console.log(greeter.greet());

js 中的 this 指向

  • 函数中的 this:window
  • 方法中的 this:调用他的对象
  • 构造函数中的 this:通过构造函数创建的实例
  • 事件处理函数中的 this:触发事件的对象
  • 定时器执行的 function 中的 this:window

pubic、private、protected

class A {}
class B {}

B 继承 A,A 是基类,B 是派生类,基类通常被称为超类,派生类通常被称作子类,子类如果有构造函数,它必须要调用super()

  • public:类成员(属性和方法)默认是 publc
  • private:当成员被标记成 private 时,它就不能在声明它的类的外部访问
  • protected:protected 修饰符与 private 修饰符的行为很相似,但有一点不同, protected 成员在派生类中仍然可以访问

readonly 修饰符

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

参数属性

存取器

首先,存取器要求你将编译器设置为输出 ECMAScript 5 或更高。 不支持降级到 ECMAScript 3。 其次,只带有 get 不带有 set 的存取器自动被推断为 readonly 。

静态属性

静态属性存在于类本身上面而不是类的实例上,关键字 static 定义静态属性,实例属性使用 this 访问,静态属性通过类名调用。

抽象类

关键字 abstract 定义抽象类和在抽象类内部定义抽象方法。

示例:

abstract class Animal {
  abstract makeSound(): void;
  move(): void {
    console.log("roaming the earth...");
  }
}

高级技巧

构造函数

class Greeter {
  static standardGreeting = "Hello, there";
  greet() {
    return Greeter.standardGreeting;
  }
}
let greeter1: Greeter = new Greeter();
console.log(greeter1.greet()); // Hello, there

// `typeof Greeter`取Greeter类的类型,而不是实例的类型。"告诉我Greeter标识符的类型",也就是构造函数的类型
// 这个类型包含了类的所有静态成员和构造函数
let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";

let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet()); // Hey there!

把类当做接口使用