TypeScript
一、快速入门
1.1 TypeScript简介
- TypeScript是JavaScript的超集。
- 它对JS进行了扩展,向JS中引入了类型的概念,并添加了许多新的特性。
- TS代码需要通过编译器编译为JS,然后再交由JS解析器执行。
- TS完全兼容JS,换言之,任何的JS代码都可以直接当成TS使用。
- 相较于JS而言,TS拥有了静态类型,更加严格的语法,更强大的功能;TS可以在代码执行前就完成代码的检查,减小了运行时异常的出现的几率;TS代码可以编译为任意版本的JS代码,可有效解决不同JS运行环境的兼容问题;同样的功能,TS的代码量要大于JS,但由于TS的代码结构更加清晰,变量类型更加明确,在后期代码的维护中TS却远远胜于JS。
1.2 TypeScript 开发环境搭建
下载安装Node.js
使用npm全局安装typescript
npm i -g typescript
创建一个ts文件
使用tsc对ts文件进行编译
在ts文件所在目录 执行命令:tsc xxx.ts
二、基本类型
2.1 类型声明
类型声明是TS非常重要的一个特点
通过类型声明可以指定TS中变量(形参,返回值)的类型
指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,符合则赋值,否则报错
类型声明给变量设置了类型,使得变量只能存储某种类型的值
// 在JS中是不限制变量的类型的
// 先声明再赋值
let a: string; // 变量a只能存储字符串类型
let b: number; // 变量b只能存储数值
let c: boolean; // 变量c只能存储布尔值
a = "hello";
// a = 100 //警告:不能将类型“number”分配给类型“string”
b = 666;
// b = "aa" //警告:不能将类型“string”分配给类型“number”
c = true;
// c = 12 //警告:不能将类型“number”分配给类型“boolean”
// JS 中的函数是不考虑参数个数和类型的
// 为函数的参数,和返回值 指定类型
function demo(x: number, y: number): number {
return x + y;
}
demo(1, 2);
// demo(1,2,3) // 警告:应有 2 个参数,但获得 3 个
// demo("13")// 警告:应有 2 个参数,但获得 1 个注意:
类型名为小写
当为变量声明了类型后以后再对变量进行赋值时,若类型不符就会报错
2.2 类型推断
在声明变量并赋值时不必声明变量类型,TypeScript会推断出变量的类型
// 变量声明时赋值(常用)
let d = -99; //TypeScript会推断出变量d的类型是数字
// let d: number = -99; // 等同于上一行代码
// d = false;//警告:不能将类型“boolean”分配给类型“number”2.3 类型总览
2.3.1 JavaScript 中的数据类型:
string 、 number 、 boolean 、 null 、 undefined 、 bigint 、 symbol 、 object
备注:其中 object 包含: Array 、 Function 、 Date ......
2.3.2 TypeScript 中的数据类型:
- 以上所有
- 六个新类型:
void、never、unknown、any、enum、tuple - 自定义类型:
type、interface
注意点: JS 中的这三个构造函数: Number 、 String 、 Boolean ,他们只用于包装对象,正常开发时,很少去他们,在 TS 中也是同理。
2.3.3 类型总览:
| 类型 | 例子 | 描述 |
|---|---|---|
| number | 1, -33, 2.5 | 任意数字 |
| string | 'hi', "hi", hi | 任意字符串 |
| boolean | true、false | 布尔值true或false |
| 字面量 | 其本身 | 限制变量的值就是该字面量的值 |
| any | * | 任意类型 |
| unknown | * | 类型安全的any |
| void | 空值(undefined) | 没有值(或undefined) |
| never | 没有值 | 不能是任何值 |
| object | 任意的JS对象 | |
| tuple | [4,5] | 元组,TS新增类型,固定长度数组 |
| enum | "enum{A, B}" | 枚举,TS中新增类型 |
2.4 常用类型
2.4.1 字面量:
也可以使用字面量去指定变量的类型,通过字面量可以确定变量的取值范围
let a: "你好"; // a的值只能为字符串"你好"
let b: 100; // b的值只能是数字100
// a = "1" //警告:不能将类型"1"分配给类型"你好"
// b = 200; //警告:不能将类型“200”分配给类型“100”
let gender: "男" | "女"; //定义⼀个gender变量,值只能为字符串“男”或“⼥”
gender = "男";
// gender = "未知"; //不能将类型“"未知"”分配给类型“"男" | "⼥"”
let c: number | string; //定义一个c变量,值可以是数字或字符串
// |:或 联合类型:number | string
c = 100;
c = "100";2.4.2 any
any 的含义是:任意类型,一旦将变量类型限制为any,那么就意味着放弃了对该变量的类型检查。
// 明确的表示a的类型时any --- 显示的any
let a: any;
// 以下对a的赋值,均⽆警告
a = 1;
a = "1";
a = true;
// 声明变量时不指定类型,则TS解析器会自动判断类型为any --- 隐式any
let b;
//以下对b的赋值,均⽆警告
b = 1;
b = "2";
b = true;
// 注意:any类型的变量,可以赋值给任意类型的变量(在开发中一般不要使用 )
let e;
e = 1;
let f: string;
f = e; // 无警告 但会影响s的类型检测,导致运行时出错
// 此时f的值与f的类型不同,发生错误,但不会出现警告注意:
any类型的变量,可以赋值给任意类型的变量(影响被赋值变量的类型检查,导致出现其值与类型 不匹配的错误)
2.4.3 unknown
unknown 的含义是:未知类型,随着变量的动态赋值变量的类型也会随之改变。
unknown 可以理解为一个类型安全的 any (不能直接赋值给其他变量)
unknown 适用于:开始不知道数据的具体类型,后期才能确定数据的类型
// 设置a的类型为unknown
let a: unknown;
// /以下对a的赋值,均正常
a = 1;
console.log(typeof a); // number
a = "2";
console.log(typeof a); // string
a = false;
console.log(typeof a); // boolean
// 随着对a的动态赋值a的类型也会随之改变
// 设置x的数据类型为string
let x = "abc";
// x = a //警告:不能将类型“unknown”分配给类型“string”
// 若想将 a 赋值给 x 有如下三种写法:
// 第一种方法:加类型判断,当a的类型为string时,才赋值给x,避免出现类型错误
if (typeof a === "string") {
x = a;
}
// 第二种方式:类型断言 告诉解析器变量的实际类型
x = a as string;
// 第三种方式:类型断言 告诉解析器变量的实际类型
x = <string>a;any 与 unknown 的区别:
- any可以赋值给任意类型的变量而unknown不可以
- any 后点任何的东西都不会报错,而unknown 正好与之相反。
TypeScriptlet str1: string = 'hello' str1.toUpperCase() //⽆警告 let str2: any = 'hello' str2.toUpperCase() //⽆警告 let str3: unknown = 'hello'; str3.toUpperCase() //警告:“str3”的类型为“未知” // 使⽤断⾔强制指定str3的类型为string (str3 as string).toUpperCase() //⽆警告
2.4.4 never
never 的含义是:任何值都不是,简而言之就是不能有值,undefined、null、''、0 都不行!
- 几乎不用
never去直接限制变量,因为没有意义
// 指定a的类型为never,那就意味着a以后不存在任何的数据了
let a: never;
// 以下对a的赋值都会有警告
// a = 1
// a = true
// a = undefined
// a = nullnever一般是 TypeScript 主动推断出来的
// 指定b的类型为string
let b:string
// 给b设置一个值
b = "hello"
if(typeof b === "string"){
b.toUpperCase()
}else{
console.log(b);
// TypeScript会推断出此处的b是never,因为没有任何一值符合此处的逻辑
}
// & 同时满足
let c: string & number
// 此处c的类型是nevernever也可以用于限制函数的返回值
function demo():never{
throw new Error("程序异常退出")
}2.4.5 void
viod 的含义是:空 或 undefined ,严格模式下不能将 null 赋值给 void 类型
let a: void = undefined;
// 严格模式下,该行会有警告:不能将 null 分配给类型 void
// let b:void = null;void 常用与限制函数返回值
// 无警告
function demo1(): void {}
// 无警告
function demo2(): void {
return;
}
// 无警告
function demo3(): void {
return undefined;
}
let demo4 = (): void => {
// return 666; // 有警告:不能将类型“number”分配给类型“void”
};2.4.6 object
object的含义:任何【非原始值类型】,包括:对象、函数、数组等,范围较广。一般不用于类型限制
let a:object //a的值可以是任何【⾮原始值类型】,包括:对象、函数、数组等
// 以下代码,是将【⾮原始类型】赋给a,所以均⽆警告
a = {}
a = {name:'张三'}
a = [1,3,5,7,9]
a = function(){}
// 以下代码,是将【原始类型】赋给a,有警告
a = null // 警告:不能将类型“null”分配给类型“object”
a = undefined // 警告:不能将类型“undefined”分配给类型“object”
a = 1 // 警告:不能将类型“number”分配给类型“object”
a = true // 警告:不能将类型“boolean”分配给类型“object”
a = '你好' // 警告:不能将类型“string”分配给类型“object”Object的含义:Object的实例对象,限制的范围太大了,几乎不用。
let a:Object //a的值必须是Object的实例对象,
// 以下代码,均⽆警告,因为给a赋的值,都是Object的实例对象
a = {}
a = {name:'张三'}
a = [1,3,5,7,9]
a = function(){}
a = 1 // 1不是Object的实例对象,但其包装对象是Object的实例
a = true // truue不是Object的实例对象,但其包装对象是Object的实例
a = '你好' // “你好”不是Object的实例对象,但其包装对象是Object的实例
// 以下代码均有警告
a = null // 警告:不能将类型“null”分配给类型“Object”
a = undefined // 警告:不能将类型“undefined”分配给类型“Object”- 在实际开发中,限制一般对象,通常使用以下形式
语法:"let 对象名: { 属性1: 类型; 属性2?: 类型; ...[propName: string]: any}"
? 表示该属性是可选的
【[key: string]: any】 表示任意字符串的属性名和任意的类型
// 限制person对象的具体内容,使用【,】分割,【?】代表可选属性
let person: { name: string, age?: number };
// 限制car对象的具体内容,使用【;】分割,必须有price和color属性,其它属性不去限制,有没有都行 【[propName: string]: any 】表示任意属性
let car: { price: number; color: string; [propName: string]: any };
// 限制student对象的具体内容,使用【回车】分割
let student: {
id: string
grade: number
};
// 以下代码均无警告
person = {name: "Tom"}
person = {name: "Tom", age: 18}
car = {price:100, color:'pink'}
student = {id: '001', grade: 3}限制函数的参数,返回值,使用以下形式
语法:let 函数名: (形参1: 类型,形参2: 类型,...)=> 返回值类型
let demo: (a: string, b: number) => string;
// 显示函数的参数类型和返回值类型
demo = (x, y) => {
return x + y;
};
// 下面函数有警告
demo = (x, y ,z) => {
return x + y;
}限制数组,使用以下形式
语法:类型[] 或 Array<类型>
let arr1: number[]; // 限制arr1只能存储数字
let arr2: Array<number>; // 等价于上一行代码
let arr3: string[]; // 限制aar3只能存储字符串
let arr4: Array<string>; // 等价于上一行代码
arr1 = [1, 2, 3];
arr3 = ["a", "b", "c"];tips: 在开发中数组中一般存储相同类型的数据
2.4.7 tuple
tuple 就是一个固定长度的数组
语法:[类型, 类型, 类型, ....]
let t: [string, number];
t = ["hello", 123];
// 警告,不能将类型“[string, number, boolean]”分配给类型“[string, number]”
// t = ["hello", 123, true];
// 长度和类型必须一致2.4.8 enum
enum(枚举):将所有可能的情况列举出来
// 定义一个枚举
enum Color {
Red,
Green,
Blue,
}
// 定义一个枚举,并指定其初始数值
enum Color2 {
Red = 1,
Green,
Blue,
}
console.log(Color);
/*
{ '0': 'Red', '1': 'Green', '2': 'Blue', Red: 0, Green: 1, Blue: 2 }
*/
console.log(Color2);
/*
{ '1': 'Red', '2': 'Green', '3': 'Blue', Red: 1, Green: 2, Blue: 3 }
*/
// 定义一个phone变量,并设置对其进行限制
let phone: { name: string; price: number; color: Color };
phone = { name: "华为", price: 2000, color: Color.Red };
phone = { name: "iphone", price: 6000, color: Color.Green };
if (phone.color === Color.Red) {
console.log("手机是红色的");
}2.4 | &
| 表示或:满足一种类型即可
let m: string | number; // m的类型为 string或muber
m = "123";
m = 12;&表示与:表示同时满足的类型
let j: string & number; // j的类型同时为string和number
j = "123"; // 报错 j的类型为never
let k: {name:string} & {age:number}; // k同时拥有name和age属性
k = {
name:"张三",
age:19
}2.5 自定义类型
为类型起一个别名,更加灵活的限制类型
// 性别枚举
enum Gender {
Mael,
Female,
}
// 自定义一个年级(高一、高二、高三)
type Grade = 1 | 2 | 3;
// 自定义一个学生类型
type Student = {
name: string;
age: number;
gender: Gender;
grade: Grade;
};
// 定义两个学生变量:s1、s2
let s1: Student;
let s2: Student;
s1 = { name: "张三", age: 18, gender: Gender.Mael, grade: 1 };
s2 = { name: "李四", age: 20, gender: Gender.Female, grade: 3 };2.6 类型断言
在有些情况下,unknown 或 any 类型的变量的实际类型我们是明确的,但TS编译器却不清楚,此时可以通过类型断言来告诉编译器变量的类型
写法一:
变量名 as 类型
let a:unknown = "猜猜我的长度"; // a的类型为 unknown
let leng:number = (a as string).length; // 通过类型断言设置a的类型为 string写法二:
<类型>变量名
let a:unknown = "猜猜我的长度";
let leng:number = (<string>a).length三、面向对象
在ES6类的基础上进行了扩展增加了抽象类、接口、泛型...
3.1 常规类
class Person {
// 定义属性
name: string;
age: number;
// 使用构造函数,初始化属性(在对象创建时调用)
constructor(name: string, age: number) {
this.name = name;
this.age = age;
console.log(this); // this指向实例对象
}
// 实例方法
sayHello() {
console.log("hello");
}
}
const p1 = new Person("张三", 18);
const p2 = new Person("李四", 19);
console.log(p1);
console.log(p2);与ES6类的定义多了一个属性定义和指定参数类型的步骤
this:表示当前对象
3.2 继承(extends)
子类会拥有父类所有的属性和方法(私有属性和方法除外)
// 定义父类 Animal
class Animal {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`我是${this.name}今年${this.age}岁`);
}
}
// 定义子类 Dog 继承 Animal
class Dog extends Animal {
color: string; // 添加子类独有的属性
constructor(naem: string, age: number, color: string) {
super(naem, age); // 调用父类的构造函数
this.color = color;
}
// 重写父类中的方法
sayHello() {
console.log(`我是${this.name}今年${this.age}岁,我的颜色是${this.color}`);
}
// 子类独有的方法
run() {
console.log("小狗跑得快!!");
}
}
const dog = new Dog("旺财", 2, "黑色");
console.log(dog);
dog.sayHello();
dog.run();使用 extends 关键字继承父类的属性和方法
super 表示当前类的父类
在constructor 中使用 spuer() 调用父类中的构造方法
3.3 抽象类
不能去实例化,只能被别人继承,抽象类里可以有抽象方法(抽象方法:abstract开头,没有方法体)
// 定义抽象类
abstract class Animal {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// 抽象方法:abstract开头,没有方法体
// 抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写
abstract sayHello(): void; // :void返回值类型
普通方法...
}
// 定义子类 Dog 继承 Animal
class Dog extends Animal {
// 添加子类独有的属性
color: string;
constructor(naem: string, age: number, color: string) {
super(naem, age); // 调用父类的构造函数
this.color = color;
}
// 重写父类中的方法
sayHello() {
console.log(`我是${this.name}今年${this.age}岁,我的颜色是${this.color}`);
}
}
const dog = new Dog("旺财", 2, "黑色");
console.log(dog);
dog.sayHello();
// const animal = new Animal("蛇", 3); // 警告:无法创建抽象类的实例抽象类:在 class 前加 abstract ,不能被实例化
抽象方法:在方法名前加 abstract,没有方法体
注意:抽象方法只能被定义在抽象类中,且子类必须对抽象方法进行重写
3.4 接口
- 接口是用来限制类中包含哪些属性和方法
// 使用接口来限制类的结构,实现了该接口的类必须要满足接口定义的属性和方法
interface myInter {
name: string;
asyHello(): void;
}
// 实现接口就是使类满足接口的要求
class Myclass implements myInter {
// 必须要有name属性
name: string;
constructor(name: string) {
this.name = name;
}
// 必须要有asyHello方法
asyHello() {
console.log("hello");
}
.....
}使用
implements去实现接口实现了该接口的类必须要满足接口定义的属性和方法
- 接口是可以重复声明的
接口重复声明内的属性和方法会进行追加
interface myInterface {
// 应该具有的属性
name: string;
age: number;
// 应该具有的方法
getAge(): number;
}
interface myInterface {
// 新增的属性
sex: string;
}
const obj: myInterface = {
name: "李四",
age: 20,
sex: "男",
getAge() {
return this.age;
},
};- 接口 与 自定义类型 的区别
接口可以:
- 当自定义类型去使用
- 可以限制类的结构
自定义类型:
- 仅仅就是自定义类型
// 描述一个对象的类型:自定义类型
type myType = {
// 应该具有的属性
name: string;
age: number;
// 应该具有的方法
getAge(): number;
};
const obj1: myType = {
name: "张三",
age: 18,
getAge() {
return this.age;
},
};
// 接口就是用来定义一个类的结构,类中包含哪些属性和方法
interface myInterface {
// 应该具有的属性
name: string;
age: number;
// 应该具有的方法
getAge(): number;
}
// 接口做为自定义类型使用
const obj2: myInterface = {
name: "李四",
age: 20,
getAge() {
return this.age;
},
};- 接口 与 抽象类 的区别
接口:(父类对子类的一种限制)
- 只能有抽象类,只定义结构,而不考虑实际值,所有的属性都不能有实际的值
- 使用
implements关键字去实现接口- 子类不会继承到父类任何东西
抽象类:(父类提供给子类继承使用)
- 可以有普通方法,也可以有抽象方法
- 使用
extends关键字去继承抽象类- 子类可以继承到父类属性和方法
抽象类举例:
// 抽象类中可以有抽象方法也可以有普通方法
abstract class Person {
// 属性
name: string;
age: number;
// 构造器
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// 抽象方法
abstract speak(): void;
// 普通方法
eat() {
console.log("吃饭");
}
}
// Teacher类继承抽象类Person
class Teacher extends Person {
constructor(name: string, age: number) {
super(name, age); // 调用抽象类的构造器
}
// 重写抽象方法
speak(): void {
console.log("老师说话");
}
}接口举例:
interface Person {
// 属性,不写具体指
name: string;
age: number;
// 方法,不写具体实现
speak(): void;
}
// 创建Teacher类实现Person接口
class Teacher implements Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
speak() {
console.log("我是一个老师");
}
}3.5 属性修饰符
| 修饰符 | 描述 | 作用 |
|---|---|---|
| readyonly | 只读属性 | 属性无法修改 |
| public(默认值) | 公开的 | 可以在类、子类和对象中访问(修改) |
| protected | 受保护的 | 可以在类,子类中访问(修改) |
| private | 私有的 | 只能在类中访问(修改) |
class A {
readonly name: string;
public age: number;
protected sex: string;
private tel: string;
constructor(name: string, age: number, sex: string, tel: string) {
this.name = name;
this.age = age;
this.sex = sex;
this.tel = tel;
}
test() {
console.log(this.name);
console.log(this.age);
console.log(this.sex);
console.log(this.tel);
}
}
class B extends A {
constructor(name: string, age: number, sex: string, tel: string) {
super(name, age, sex, tel);
}
test() {
console.log(this.name);
console.log(this.age);
console.log(this.sex);
// protected修饰的属性不能在子类中访问
// console.log(this.tel); // 警告 tel私有只能在A中访问
}
}
const b = new B("李四", 19, "男", "123456789");
console.log(b.name);
console.log(b.age);
// protected和private修饰的属性不能在对象中访问
// console.log(b.sex); // 警告 sex受保护只能在A和A的子类中访问
// console.log(b.tel);// 警告 tel私有只能在A中访问3.6 属性的封装
- 自定义getter、setter方法
class Person {
// TS 可以在属性前添加属性的修饰符
private name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
// 通过公共的方法读取(修改)属性值
getName() {
return this.name;
}
setName(name: string) {
this.name = name;
}
getAge() {
return this.age;
}
setAge(age: number) {
if (age >= 0) {
this.age = age;
}
}
}
const per = new Person("张三", 18);
// 若使用public,属性可以任意的修改,将会导致对象中的数据变得不安全
// per.name = "李四"; // // 警告:属性name为私有属性 只能在类的内部访问
// per.age = -20; // // 警告:属性name为私有属性 只能在类的内部访问
// console.log(per.name); // 警告:属性name为私有属性 只能在类的内部访问
// 通过公共方法去访问(修改)私有属性
console.log(per.getName());
per.setName("李四");
console.log(per.getName());
per.setAge(-18);
console.log(per.getAge()); //18- 使用 ES6/TS 中的 getter、setter方法
// 定义一个表示人的类
class Person {
// TS 可以在属性前添加属性的修饰符
private _name: string;
private _age: number;
constructor(name: string, age: number) {
this._name = name;
this._age = age;
}
// TS中设置getter setter方法的方式
get name() {
// 获取name属性时自动调用
return this._name;
}
set name(value: string) {
// 修改name属性时自动调用
this._name = value;
}
get age() {
return this._age;
}
set age(value: number) {
if (value >= 0) {
this._age = value;
}
}
}
const per = new Person("张三", 18);
// console.log(per._name); // 警告:name为私有属性 只能在类的内部访问
console.log(per.name, per.age); // 张三 18
per.name = "李四";
per.age = -20;
console.log(per.name, per.age); // 李四 183.7 类定义的简写形式
普通写法:
先声明属性,在使用构造函数进行初始化
class C {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
class D extends C {
sex: string;
constructor(name: string, age: number, sex: string) {
super(name, age);
this.sex = sex;
}
}简化写法:
直接将属性定义在构造函数中,不用再写属性声明和构造函数中的属性初始化
class F {
//构造函数参数必须写上属性修饰符和类型,否则报错
constructor(public name: string, public age: number) {}
}
class E extends F {
constructor(name: string, age: number, public sex: string) {
super(name, age);
}
}
const e = new E("Tom", 18, "male");
console.log(e);// E { name: 'Tom', age: 18, sex: 'male' }特别注意:构造函数参数必须写上属性修饰符和类型,否则报错
3.8 泛型
定义⼀个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),此时就需要泛型了
<T> 就是泛型,(T可以为其他字母),设置泛型后即可在函数中使用 T 来表示该类型:
// <T> 就是一个泛型
function fn<T>(a: T): T {
return a;
}
// 可以直接去调用具有泛型的函数
fn(10); // TS会自动推断T的类型为number
fn<string>("hello"); // 手动指定T的类型为string在函数调用时指定其类型
泛型可以写多个
function fn2<T, K>(a: T, b: K): T {
console.log(b);
return a;
}
// 为多个泛型指定其类型
let result3 = fn2<string, number>("1", 2);对泛型的范围进行约束
interface Inter {
lengh: number;
}
// T extends Inter 表示泛型T必须是Inter的子类
function fn3<T extends Inter>(a: T): number {
return a.lengh;
}
fn3({ lengh: 10 });在类中使用泛型
// 在类中使用泛型
class MyClass<T> {
name: T; // 不确定name的类型,使用变量表示类型
constructor(name: T) {
this.name = name;
}
}
const mc = new MyClass<string>("张三"); // 在实例化的时候指定泛型T的类型