Alan

此刻想举重若轻,之前必要负重前行

TypeScript常用知识点总结

什么是TypeScript

TypeScript是JavaScript的一个超集,在JavaScript的基础上增加了可选的静态类型和基于类的面向对象编程。它可以编译成纯JavaScript,未编译的ts代码无法在浏览器执行。我们可以把它和JavaScript的关系理解成css和less、sass的关系。

TypeScript好在哪里

  • TS可以进行动态类型检测,可以检测出一些潜在的bug(例如拼写错误、参数缺失、undefined等),提升代码健壮性
  • 使用vscode进行开发可以很好的提示代码,提高开发效率
  • 代码可读性好

创建第一个TypeScript文件

首先安装TypeScript

npm install -g typescript

安装后看是否成功

tsc --version

我安装后出现了以下问题:
image-20200614175730166
这种情况只需要以管理员的身份打开命令行运行以下命令:

set-ExecutionPolicy RemoteSigned

创建第一个ts文件Hello.ts

function sayHello(name:String) {
  console.log(`Hello ${name}`);
}
let person = "Alan";
sayHello(person);

我们发现ts代码和普通的js代码在sayHello函数的参数上有所不同。
sayHello(name: String)
大致的意思就是sayHello传入一个名为name的参数,该参数的类型必须是String,不然无法通过ts的编译。
代码写好后用tsc编译上面的Hello.ts文件

tsc Hello.ts

编译成功后在同级目录下生成一个Hello.js文件,可以看到生成的js文件只是将es6语法转化成了es5语法,并没有改变其他代码。

function sayHello(name) {
    console.log("Hello " + name);
}
var person = "Alan";
sayHello(person);

如果把Hello.ts文件改写一下

function sayHello(name:String) {
  const text = 3 + name;
  console.log(`Hello ${name}`);
}
let person = 123;
sayHello(person);

再次编译发现会报错但是仍然能生成js文件:
image-20200614180440953

上面错误的具体意思是

  • 由于声明了函数形参name为静态类型string,而在调用时传入得是number类型123,与前面的string不符。
  • sayHello函数中将name(string)3(number)两种不同类型得值进行了相加。

我们发现每次都要通过tsc来编译ts文件能得到js文件后再去运行js文件,过于麻烦。可以使用插件ts-node来直接运行ts文件。

npm i ts-node -g
ts-node Hello.ts

ts能够尝试分析变量类型(类型推断),ts无法分析出的变量最好显式声明变量类型(类型注解)

基础类型

变量的声明:let [变量名] : [类型] = 值

例如: let age: number = 21

TypeScript支持与JavaScript几乎相同的数据类型

  • boolean(布尔值)
  • number(数值)
  • string(字符串)
  • []/Array<元素类型>(数组)
  • 元组Tuple
  • enum(枚举)
  • any(任何值)
  • void(空值)
  • null
  • undefined
  • never

数组

有2种方式定义数组

let arr : number[] = [1,2,3] // 元素类型后接上`[]`
let arr : Array<number> = [1,2,3] // 数组泛型
let arr: (number | string)[] = [1, '2', '4'] // 元素类型可以是number或string(类似元组)

元组tuple

表示一个数组(各个元素的类型不必相同)

let list : [string, number]; //第一个元素为string类型,第二个为number类型
a = ['abc', 123] //合格
b = [123, 'abc'] //不合格

枚举

其实有点类似对象
看例子吧

enum lan {js, ts, css};
console.log(lan.js); // 0   js对应的下标,第一个默认下标为0

enum lan {
  js = 3,
  ts,
  css,
}
console.log(lan.js); // 3
console.log(lan.ts); // 4
console.log(lan.css); // 5

enum lan {
  js,
  ts = 3,
  css,
}
console.log(lan.js); // 0
console.log(lan.ts); // 3
console.log(lan.css); // 4
console.log(lan[4]); // css
console.log(lan[1]); // undefined
// 第一个默认下标为0,css接着ts的值+1

enum lan {js = 'good', ts = 'nice', css = 'well'};
console.log(lan.js); // good

any

顾名思义,任意值,当我们想为还不清楚类型的变量指定一个类型时,any就是最好的选择🤭

let a:any =4;
a = '123'; //合格


let arr : any[] = [1, '123', true]; // 和元组很像
arr[1] = 'good';

void

常用于无返回值的函数声明

function func() : void {
  console.log('learning typescript...');
}
func(); //合格


function func() : void {
  return 1;
}
func(); // 不合格:Type '1' is not assignable to type 'void'

null和undefined

用处不大,默认情况下是所有类型的子类型,例如下面代码是没有问题的:

let a:string;
a = undefined;
a = null;

但是,当指定--strictNullChecks标记时,null和undefined只能赋值给void和他们自己本身。

never

表示永不存在的值的类型
never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型;
变量也可能是never类型,当它们被永不为真的类型保护所约束时。

never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使any也不可以赋值给never。

function error(message:string) : never{
  throw new Error(message);
}

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

结构赋值的写法

function sayAge({name, age}: {name: string | number, age: number}): void {
  console.log(`${name} is ${age} years old`);
}
sayAge({name: 'Alan', age: 22})
sayAge({name: 2, age: 22})

联合类型

|来表示取值存在多种可能

let a: string | number;
a = 1;
a = 'A';

类型断言

可以自己指定一个值的类型
格式如下
<类型>值
值 as 类型

interface student {
  isStudent: boolean;
  education: number;
}
interface worker {
  isStudent: boolean;
  seniority: number;
}
function Recruit(candidate: worker | student): void {
  if (candidate.isStudent) {
    console.log(`your education is ${(candidate as student).education}`);
  } else {
    console.log(`your seniority is ${(candidate as worker).seniority}`);
  }
}

const a: student = { isStudent: true, education: 4 };
const b: worker = { isStudent: false, seniority: 2 };
Recruit(a);
Recruit(b);

由于candidate使用了联合类型,所以ts无法判断candidate究竟是属于student还是worker,所以要使用类型断言来显式告诉ts。

当然还有其他方式来实现

interface student {
  isStudent: boolean;
  education: number;
}
interface worker {
  isStudent: boolean;
  seniority: number;
}
function Recruit(candidate: worker | student): void {
  'education' in candidate &&
    console.log(`your education is ${candidate.education}`);
  'seniority' in candidate &&
    console.log(`your seniority is ${candidate.seniority}`);
}

const a: student = { isStudent: true, education: 4 };
const b: worker = { isStudent: false, seniority: 2 };
Recruit(a);
Recruit(b);
  • typeof
  • instanceof

接口interface

先看一个接口的例子

interface person {
  name: string;
}
function work(personObj:person) {
  console.log(personObj.name);
}
let person1 = {name: 'Alan', age: 21};
work(person1);
let person2 = {age: 21};
work(person2);

image-20200615180720618

定义接口的关键字是interface
这个例子需要传入work的参数必须是一个带有name(string)的对象,可以理解为我要招聘一个有名字的员工,没有名字的都不需要。当然我们的person1中还多了一个age属性,这并不会报错。可是当我们直接传递参数时是会出错的。

work({name: 'Alan', age: 21});

那如果我不确定要招聘的员工除了有姓名外还需要其他什么属性时,可以像下面一样重新定义接口就可以解决上面的问题了。

interface person {
  name: string;
  [propName: string]: any
}

可选属性

那如果我的招聘条件是最好懂typescript的,这个时候typescript就是可有可无的(最好懂,嘿嘿😜),那我们就要用到可选属性
可选属性在可选属性名后面加上一个?

interface person {
  name: string;
  ts?: boolean;
}
function Recruit(personObj:person) : string{
  if(personObj.ts) {
    return `congratulations!${personObj.name}`;
  } else {
    return `sorry,${personObj.name}, we need a employee who know ts!`;
  }
}
let person1 = {name: 'Alan', age: 21, ts: true};
console.log(Recruit(person1));
let person2 = {name: 'Bob', age: 21};
console.log(Recruit(person2));

只读属性

我们都知道人的名字都是不可以改变的(一般情况下),这个时候我们对person接口里面的name属性稍作修改,在属性名name前加上readonly

当然也可以使用setter/getter来实现

interface person {
  readonly name: string;
  ts?: boolean;
}
let person1: person = {name: 'Alan'};
person1.name = 'Bob'; // Cannot assign to 'name' because it is a read-only property.

我们发现readonly和const的作用好像有点相似,那我们什么时候使用readonly什么时候使用const呢?
变量—->const
属性—->readonly

函数类型的接口

接口除了可以描述带有属性的对象外,还可以描述函数类型
这里创建一个函数来检查你有没有打卡😁

interface attendanceFunc {
  (name:string, startTime: number, endTime: number) : boolean;
}
let checkAttendance : attendanceFunc;
checkAttendance = function (name:string, startTime: number, endTime: number) : boolean {
  let result = startTime < 9 && endTime >18;
  return result;
}
console.log(checkAttendance('Alan', 10, 19)); // false

看一下接口的声明:

interface attendanceFunc {
  (name:string, startTime: number, endTime: number) : boolean;
}

name,startTime,endTime放在()中代表函数的参数
:boolean表示函数的返回值类型
当然上面例子中的checkAttendance的形参以及函数的返回值来说可以不指定类型,因为checkAttendance复制给了attendanceFunc变量,类型检查器会自动(按照接口中参数的顺序)推断出参数以及返回值的类型,也就是说写成下面这样也是可以的。函数中的参数名可以不和接口中的相同

interface attendanceFunc {
  (name:string, startTime: number, endTime: number) : boolean;
}
let checkAttendance : attendanceFunc;
checkAttendance = function (n, startTime, endTime) {
  let result = startTime < 9 && endTime >18;
  return result;
}
console.log(checkAttendance('Alan', 10, 19)); // false

接口的继承

一个接口可以继承1个或者多个接口:
继承使用关键词extends

interface person {
  name : string;
}
interface student {
  studentId : number;
}
interface seniorStudent extends person, student {
  grade: string;
}
let student1 : seniorStudent = {name: 'Alan', studentId: 1, grade: 'one'};
console.log(student1);

类class

TS中的类和ES6中的类很相似,这里只介绍不同的地方

变量修饰符

  • public(默认)
  • private(私有,不能在声明它的类的外部访问)
  • protected(和private类似,不同的是protected声明的变量可以在派生类(即子类)中访问)

静态属性static

class Person {
  static fingerNum = 5;
}

// 只能通过类来访问
console.log(Person.fingerNum);
// 不能通过实例访问
console.log(new Person().fingerNum);
// 单例模式创建唯一的实例
class singleClass {
  private static instance: singleClass;
  private constructor(public name: string) {}

  static getInstance() {
    if (!this.instance) {
      this.instance = new singleClass('Alan');
    }
    return this.instance;
  }
}

const class1 = singleClass.getInstance();
const class2 = singleClass.getInstance();
console.log(class1.name); // Alan
console.log(class1 === class2); // true

构造函数

class Person {
  constructor(name, mobile, sex) {
    this.name = name;
    this.mobile = mobile;
    this.sex = sex;
  }
  public name: string;
  private mobile: string;
  protected sex: string;
}

上面的代码可以简写成下面这样

class Person {
  constructor(name: string, private mobile: string, protected sex: string) {
  }
}

抽象类

  • 不能被实例化
  • 要使用的话要声明其派生类,并且重写其中的抽象方法
abstract class Animal {
  constructor(public name: string) {}
  sayHello() {
    console.log('hello');
  }
  // 声明抽象方法
  abstract action(): void;
}

class Bird extends Animal{
  constructor(name) {
    super(name);
  }
  action() {
    console.log('jijiji');
  }
}

const bird = new Bird('qc');
console.log(bird);
// Bird { name: 'qc' }

泛型

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

用法:<泛型名>

function Recruit<T>(name: string, props: T) {
  return name + props;
}
// 显式指定为T为number类型
console.log(Recruit<number>('Alan', 123)); // Alan123
// TS自动推断为number类型,和上面效果一样
console.log(Recruit('Alan', 123)); // Alan123
console.log(Recruit('Alan', [1, 2, 3])); // Alan1,2,3

泛型约束

试用extends约束泛型

interface info {
  mobile: string;
}

// T泛型必须满足info
function Recruit<T extends info>(name: string, props: T) {
  return name + props.mobile;
}

console.log(Recruit('Alan', { mobile: '1232910830' })); // Alan1232910830

评论