TypeScript学习
本文最后更新于 2025-03-03,文章内容可能已经过时。
概述
TypeScript
是JavaScript
的一个超集
,主要侧重于代码语法类型检查
,在JS
中,定义的所有类型都是静态类型
,也就是弱类型语言
,对与定义的变量无法正确的区分和使用
,因此会出现不必要的问题产生
!
let funcTest = "func";
funcTest();
Uncaught TypeError: funcTest is not a function
当定义的
变量
不是一个function
时,并当做一个function
去调用
时,就会报一个TypeError类型
错误!
12.toLowerCase();
Uncaught SyntaxError: Invalid or unexpected token
或者试着
调用一个不存在的方法
,就会在代码运行
时,出现各种未知
的问题!
从上面来看,当
JavaScript
在运行代码之前
,我们是很难确定语法类型
的相关错误问题
,这会导致
我们只有去运行代码后
,才会出现各种各样的未知问题
!
基础部分
静态类型检查
静态类型检查
,既是在编写代码的过程
中,遇到类型
,或者是错误方法
的一些不正常使用或调用
时,会即刻
在编辑器
中,通过红色波浪线
去及时反馈开发者
,此时将问题的描述会避免在代码运行时出现该问题
,总结来说就是在代码运行之前
做了静态类型
语法逻辑校验
!
非异常故障
非异常行为
,包括,方法名拼写错误、调用对象中一个不存在的属性、函数调用时未加小括号
等相关问题!
访问
对象
中不存在的属性值
,在js
中会反馈一个undefind
!const user = { name: "张三", age: 18 } user.location
拼写错误检查
const str = "Hello World"; str.toLocaleLowerCased(); // toLocaleLowerCase();
检查
逻辑语法
问题const value = Math.random() < 0.5 ? '小于' : '大于'; if( value !== '小于' ){ } else if( value === '大于' ){ }
本地使用或安装
安装
TypeScript
!npm i typescript -g
在本地创建
hello.ts
文件,ts文件本身是无法运行的,需要通过TypeScript
编译后,方可执行!const value = "hahahah"; value.toLocaleUpperCase(); console.log("Hello, world!" + value);
使用
TypeScript
进行文件编译
tsc hello.ts
- 编译后,会在
本地生成一个.js的文件
,可以使用node .js
来执行编译
后的文件
!
node hello.js
- 编译后,会在
TypeScript
基本命令:初始化
ts
配置文件tsconfig.js
tsc --init
自动编译
ts
文件tsc --watch
类型检测有问题时,不去触发自动编译
tsc --noEmitOnError hello.
修改入口目录和出口目录
我们可以通过
tsconfig.json
中的配置[rootDir / outDir]
来修改我们的指定目录
:
rootDir:
是我们编写.ts文件
代码的存放位置
!
outDir:
ts
将 .ts 文件源代码编译
后生成的.js文件
所存放的目录位置
!
├── dist
│ └── hello.js
├── src
│ └── hello.ts
└── tsconfig.json
显示类型
显示类型
,就是手动去标注参数
或变量
的类型!
function greet( name: string, date:Date){
console.log(`name: ${ name } today ${ date }`);
}
greet( "张三", new Date() );
在
typescript
中去定义变量
时,ts
会自动推断
该变量
的类型
,当试图
去修改变量值
时,若新的值的类型不符合初始值类型
时,则会报错
!
let name = "张三";
name = "李四"; // ✔️
name = 12; // x
以上代码,将初始
String
类型修改为Number
类型,这样是不允许
的!
降级编译
ts默认编
译环境为es2016
,模版字符串
等es6
相关特性
,如果在低版本浏览器中不兼容
,还需对此进行适配操作
!
tsconfig.json
,找到target
属性,其默认值为es2016
,我门修改为es5
进行降级编译
,当通过ts编译
时,就会适配es5
!
{
target: "es5"
}
常用类型
类型-string | number | boolean
在
js
中,常用的类型有[string number boolean]
!
let name: string = "张三";
let age: number = 18;
let flag: boolean = true;
以上代码,是在
ts
中定义变量
时的一种写法
!
类型-any
any
类型,从翻译表达来看,就是任意
的意思,则表示任意类型
,当某一个变量的类型为any类型
时,那么从变量赋值
,以及调用
时,则不会触发相关的类型检查
!
let obj: any = {
name: 'zhangsan',
age: 18
}
obj.foo();
obj.bar = "car";
obj();
const num:number = obj;
以上代码,
ts
类型检测正常
,可以顺利编译成.js文件
,但是当运行.js文件
时,肯定会报错
的!注意:
any
类型的变量,可以赋值
与其他任意类型
的变量
!let a: any = "hello"; let b: number = 99; b = a;
类型-unknow
unknow
的含义就是未知
的,一般用于定义个一个变量后期不知道要赋值与什么样的类型
,它与any
相比,更加安全
,因为,unknow
类型无法赋值
给其它类型的值
,而any
是可以的!
let unknownVar: unknown;
let anyVar: any;
unknownVar = 10;
unknownVar = "hello";
unknownVar = true;
// 将未知类型赋值给 string 类型变量时,需要强制类型转换 as string
// let str1: string = unknownVar; // 错误
// let str1: string = anyVar; // 可以
let str2: string = unknownVar as string;
类型-never
never
的含义是从不
,一般用于特殊函数
的返回值
,不推荐
直接在变量上使用
,因为在变量
上限制never类型
,会没有任何意义
,且被限制never的变量
则无法进行赋值
,否则会报错
!
一般用于
特殊函数
的返回值
:
无限递归函数,不会正常执行结束的函数!
函数内部抛出异常时,且中断函数执行的函数!
function demo():never {
demo();
}
function demo2():never {
throw new Error("程序出现错误!")
}
类型-void
void
的含义是空虚
的,一般用于函数
的返回值
,当函数没有返回值
时,可以使用void
来表示此函数的返回值
!
function demo1( msg: string ):void {
console.log("log msg", msg);
}
在
js
中,其函数
内部没有返回值
时,也会默认返回
一个undefined
!在
ts
中,void
可以接受
的类型其中就有undefined
!
function demo1( msg:string ):void {
console.log("log msg", msg);
return undefined;
}
function demo2( msg:string ):void {
console.log("log msg", msg);
return;
}
以上代码,在
void类型限制范围
内!
function demo1( msg:string ):void {
console.log("log msg", msg);
}
let result = demo1("你好啊!");
console.log("result", result) // 报错, void 是不允许其它变量来接受此返回值的,因为函数返回为空
function demo2( msg:string ):undefined {
console.log("log msg", msg);
}
let result2 = demo2("你好啊2!")
console.log("result", result) // 不会报错, 此处undefined也是基本类型之一,在此作为undefined返回
void 无返回值的函数
,不允许
其它变量
来接受此函数的执行结果
,因为函数没有返回值
!
类型-object(不推荐)
object
的含义
就是对象
,且该类型
的定义方向比较广泛
,数组 函数
以及class
都可以作为object的类型值
来存储
!
/*
1. object 类型表示非原始类型,可以存储任意类型的值
2. 原始类型有 number string boolean null undefined symbol
3. 原始类型不能作为 object 类型的值
*/
let obj1: object;
obj1 = {name: "Miao", age: 25};
obj1 = new String("Hello World");
obj1 = [1,2,3,4,5];
obj1 = function() {return "Hello World"};
obj1 = class Person{}
/* 无法存储原始类型 */
/* obj1 = 123;
obj1 = true;
obj1 = "Hello World";
obj1 = null;
obj1 = undefined; */
/*
1. Object 是所有类型的父类型
2. 能够访问到Object对象中的方法,都可以作为存储使用
*/
let obj2: Object;
obj2 = {name: "Miao", age: 25};
obj2 = new String("Hello World");
obj2 = [1,2,3,4,5];
obj2 = function() {return "Hello World"};
obj2 = class Person{}
/* string number boolean 是因为有对应的包装类,所以可以存储 */
obj2 = 123;
obj2 = true;
obj2 = "Hello World";
由于
object
类型的限制范围
比较广泛
,所以这边不推荐object的直接使用
!
类型-对象(推荐)
声明对象类型的方式
let person: { name: string, age: number };
// let person: { name: string; age: number };
/* let person: {
name: string
age: number
}; */
person = { name: "Miao", age: 25 };
逗号
,分号
,或者回车
都可以作为每一个属性
的分隔符
!
索引签名
索引签名
,可以帮助我们有效的在一个对象类型
中动态
添加属性
和属性值
!
let person: { name: string, age: number, [key: string]: any };
person = { name: "Miao", age: 25, gender: "male", address: "China" };
/*
[key: string]: any 索引签名,可以存储任意属性
gender: "male", address: "China" 动态添加属性值
*/
类型-数组
typescript
中定义数组类型
有两种方式:
Type[] or Array[Type]
let arr1: number[] = [1,2,3];
let arr2: Array<number> = [1,2,3];
console.log(arr1, arr2);
以上代码,
Type
用来指定数组
中的数据类型
,限制为,该数组为一个存放Number类型的数组
!
类型-函数
在
typescript
中,我们可以为函数
中的参数
来指定type类型
,同时也可以设置函数
的返回值类型
!
function sayHello( name: string, age: number ): void{
console.log(`hello ${name}, you are ${age}`);
}
sayHello('jack', 18);
void
表示该函数无返回值
操作!
function sayHello( name: string, age: number ): number{
console.log(`hello ${name}, you are ${age}`);
return age;
}
sayHello('jack', 18);
number
表示返回一个Number类型!
如果
参数
中没有指定类型
的话,则ts
则会自动推断
该参数值
的类型!
function sayHello( name, age ): number{
console.log(`hello ${name}, you are ${age}`);
return age;
}
sayHello('jack', 18);
定义函数类型
/* 定义一个函数类型,且参数包括 a 和 b,返回值是 number */
let add: (a: number, b: number) => number;
/* add = function(a: number, b: number): number {
return a + b;
} */
// 省略写法
add = function (a, b) {
return a + b;
};
=> number
此代码,并非是箭头函数语法
,且是ts
中一种形式
上的表示
和区分
的分隔符
!
可选参数
可选参数
,就是部分参数可传可不传
,但是类型
可能会为undefined
!
函数中的
可选参数
:function sayHello( name: string, age?: number ){ console.log(`hello ${name}, you are ${age}`); return age; } sayHello('jack');
函数中的
参数作为对象
时的传参
方式:function sayHello2( pt: { name: string, age: number } ){ console.log(`hello ${pt.name}, you are ${pt.age}`); return pt.age; } sayHello2({ name: 'jack', age: 18 });
如果
age
属性可传可不传
时,可以在属性后方
加入'?'
即可!function sayHello2( pt: { name: string, age?: number } ){ console.log(`hello ${pt.name}, you are ${pt?.age}`); return pt.age; } sayHello2({ name: 'jack' });
当然,在
取值age属性
时,可能为undefined
,所以通过对象的方式获取可选属性
需要通过可选连
的方式来获取
pt?.age // 避免 age 属性为undefined时 报错问题
类型-元祖
元祖类型(tuple)
,是一个特殊的数组类型
,将多种类型
,存放到一个数组
当中!
let tuple: [string, number] = ["hello", 10];
console.log(tuple[0]); // "hello"
console.log(tuple[1]); // 10
/* ? 表示可选 */
let tuple1: [string, number?] = ["hello"];
console.log(tuple[0]); // "hello"
/* ...string[] 表示任意,后面无限多个 string 类型 可写可不写 类似于 any[] */
let tuple2: [number, ...string[]] = [1, "hello", "world"];
console.log(tuple2[0]); // 1
console.log(tuple2[1]); // "hello"
console.log(tuple2[2]); // "world"
类型-class
class PersonC {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
speck(){
console.log("My name is " + this.name + " and I am " + this.age + " years old.");
}
}
const p1 = new PersonC("Alice", 25);
class StudentC extends PersonC {
grade: number;
constructor(name: string, age: number, grade: number) {
super(name, age);
this.grade = grade;
}
study() {
console.log("I am studying.");
}
override speck() {
console.log("My name is " + this.name + " and I am " + this.age + " years old and I am in grade " + this.grade + ".");
}
}
const s1 = new StudentC("Bob", 20, 3);
s1.speck();
属性修饰符
修饰符 | 含义 | 具体规则 |
---|---|---|
public |
公开的 |
类内部 、子类 、类外部 访问 |
protected |
受保护的 |
类内部 、子类 访问 |
private |
私有的 |
类内部 访问 |
readyonly |
只读的 |
属性无法修改 |
class PersonC {
public name: string;
public age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
speck() {
console.log(
"My name is " + this.name + " and I am " + this.age + " years old."
);
}
}
属性的简写形式
class PersonC {
constructor(
public name: string,
public age: number
) {}
speck() {
console.log(
"My name is " + this.name + " and I am " + this.age + " years old."
);
}
}
高级部分
联合类型
联合类型
,可以为一个变量
来指定多个类型
的表示!
let flag: number | string | boolean = true;
flag = "1"
flag = 0;
flag = false;
function getLength(str: string | string[]){
return str.length;
}
需要
注意
的是,当我们给一个变量
,或者函数参数
来定义多种type类型
时,我们在使用变量
或参数
时,需要注意各自类型
中是否存在相同的属性或方法
,避免出现无法获取该属性
和方法
的问题!
function getDetails( id: number | string ){
// console.log(`id is ${id.toUperCase()}`);
// 由于 id 可能是 number 类型,所以无法直接调用 toUperCase 方法
if(typeof id === "string"){
console.log(`id is ${id.toLowerCase()}`);
}else{
console.log(`id is ${id.toFixed()}`);
}
}
以上代码,
toLowerCase()
方法是string
中的方法,由于定义了number
类型,所以传参
时需要考虑会传递number类型
的参数,此时若是number类型
的值去调用toLowerCase
时会出现问题!
type Gender = "男" | "女";
function printGender( gender: Gender ): void {
console.log("性别: "+ gender)
}
printGender("男")
可以通过
type
来定义联合类型
,在函数传参
时,可以得到友好的提示
!
交叉类型
交叉类型
,就是在原有的类型
上进行扩展
新的属性
或方法
!
type StudentType = {
name: string;
age: number;
} & {
gender: string;
};
类型别名
类型别名
,就是将类型定义
的方式抽离到单独的定义方式
,使用该类型
时,直接通过别名
引用即可!
type Person = {
name: string;
age: number;
};
function getPerson( p: Person){
console.log(`name is ${p.name}, age is ${p.age}`);
}
getPerson({
name: "zhangsan",
age: 18
});
type Flag = number | string;
function getFlag(flag: Flag){
console.log(flag);
}
也可以通过
type
定义函数类型
,来限制函数
的参数类型
,或者函数的返回值应该是如何表现!
type FunDemo = () => void;
const funDemo: FunDemo = function () {
console.log("funDemo");
}
type FunDemo1 = (num: number) => void;
const funDemo1: FunDemo1 = function (num) {
console.log("funDemo" + num);
};
funDemo1(1);
抽象类
抽象类
,用于将属性
和方法
单独抽象出一个类
,则这个类不能被实例化
,继承抽象类的子类
,必须实现抽象类中的所定义的方法
,其内部抽象方法不能有具体的实现
!
/* 定义一个包裹 */
abstract class Package{
constructor( public weight: number ){}
// 计算运费
abstract calculateFee(): number;
// 打印包裹信息
printInfo(): void{
console.log(`包裹重量:${this.weight}kg`);
console.log(`运费:${this.calculateFee()}元`);
}
}
/* Package 是一个抽象类 不能被实例化 需要被继承后并实现其中的抽象方法*/
class Box extends Package {
/* unitPrice 子类中的属性 需要加入修饰符 否则 this.unitPrice 无法访问 */
constructor(weight: number, public unitPrice: number) {
super(weight);
}
// 计算运费 重量乘以单价
calculateFee(): number {
return this.weight * this.unitPrice;
}
}
const box = new Box(10, 5);
box.printInfo(); // 包裹重量:10kg 运费:20元
接口
接口
与类型别名
用法大致相同
,且接口能实现
的,则类型也一样可以实现
,但是类型一但被定义
且无法再次进行修改
或添加字段
!
接口
内部主要是用于定义属性规范
和方法规范
,且内部没有任何具体实现
!
定义类的结构
实现接口通过
implements
来实现
/* 定义接口 */
interface PersonInterface {
name: string;
age: number;
speck(n: number): void;
}
/* 实现接口 */
class PersonImpl implements PersonInterface {
constructor(public name: string, public age: number) {}
speck(n: number) {
console.log(`${this.name} is ${n} years old`);
}
}
const pi = new PersonImpl('Miao', 25);
pi.speck(25); // Miao is 25 years old
以上代码,
接口
定义了属性
和方法
的规范,而实现这个接口
的类
,需按照规范
去实现
!
定义对象解构
接口
在ts
中,除了可以通过implements
来实现以外,也可以当做对象
的结构
作为类型去限制
!
interface UserInterface {
name: string;
age?: number;
address: string;
readonly idCard: string;
run(): void;
}
const user: UserInterface = {
name: "Miao",
age: 25, // age 可选
address: "Beijing",
idCard: "1234567890",
run() {
console.log(`${this.name} is a runner`);
},
};
user.run(); // Miao is a runner
定义函数接口
interface CountInterface {
(a: number, b: number): number;
}
const count: CountInterface = (a, b) => a + b;
const count2: CountInterface = function (a, b) {
return a + b;
}
接口之间继承
继承接口通过
extends
来实现
/*
接口 interface
*/
interface Animal {
name: string;
classify: string;
eat(): void;
}
// 定义子接口并继承父接口的属性
interface Dog extends Animal {
run(): void;
}
// 定义子接口并继承父接口的属性
interface Cat extends Animal {
jump(): void;
}
function getDog(dog: Dog) {
console.log(dog);
}
getDog({
name: '旺财',
classify: '犬类',
eat() {
console.log('旺财在吃狗粮');
},
run() {
console.log('旺财在跑');
}
})
function getCat(cat: Cat) {
console.log(cat);
}
getCat({
name: '招财猫',
classify: '猫类',
eat() {
console.log('招财猫在吃猫粮');
},
jump() {
console.log('招财猫在跳');
}
})
以上代码,定义了一个
父接口(Animal)
,两个子接口
,分别将共用
的字段抽离成一个父接口
,其它子接口
分别去继承父接口的字段
!
接口自动合并
定义
重复
的接口,在ts
中这样是允许
的,它最终会将两个接口
不同定义的字段部分进行合并
!
interface StudentInterface {
name: string;
age: number;
grade: number;
}
interface StudentInterface {
grade: number;
}
const s: StudentInterface = {
name: "Miao",
age: 25,
grade: 3,
};
使用场景
定义
接口
的使用场景
:
定义
对象
的格式: 描述数据模型
,API响应数据格式
,配置对象
等!
类
的约定: 规定一个类
,需要哪些属性
和方法
!自动
合并
: 一般用于第三方库的类型
!
interface
与 type
区别
相同点:
两者都可以用于对象结构类型的定义与描述
!
不同点:
interface:
主要应用在对象
与类
之间的约定
,可以实现类与类之间
的实现
与继承
,以及自动合并
!
type:
一般用于定义类型
,不仅可以针对对象来约定类型
,也可以针对简单类型进行约束
,常用的有交叉类型、联合类型、类型别名
!
扩展
的方式不同:接口(
interface
)interface Dog extends Animal { run(): void; }
类型(
type
)type Dog = Animal & { run(): void; }
重复定义,并扩展新的字段
:接口(
interface
)interface Articles { title: string } interface Articles { content: string }
- 最终两个接口会
合并
- 最终两个接口会
类型(
type
)type Articles = { title: string } type Articles = { content: string }
- 最终会
报错
- 最终会
接口
仅限制Object类型
,而type
可以定义基本类型
,或复杂类型
!接口(
interface
)interface Articles { content: string }
类型(
type
)type name = string; type age = number; type friends = string[]; type Articles = { title: string }
interface
与 abstract
区别
相同点:
接口
和抽象类
都能去定义类的格式
!
不同点:
接口
只关注属性
和方法
的约定
,不关注其内部的实现
,且类
能实现多个接口
,多个接口间使用逗号分隔
!
抽象类
对于内部的抽象方法不会有具体的实现
,但是普通方法
是可以定义实现逻辑的
!
类型断言
在
TypeScript
中,类型断言(Type Assertion)
是一种告诉编译器
某个值的具体类型
的方式。它允许你手动指定一个值的类型
,从而绕过 TypeScript 的类型检查
机制。类型断言
并不会改变变量
的运行时类型
,只是在编译阶段提供类型
信息。
当我们确定一个
变量
时,此时typescript无法推断
出,此值的类型
,那么这时候我们可以手动为该value值
来指定一个类型!
其实有点像强制类型转换!
类型断言的语法
as
语法(推荐
):let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;
尖括号
语法:let someValue: any = "this is a string"; let strLength: number = (<string>someValue).length;
手动为
any类型
来手动指定
该值的确切类型
!
let obj: any = { name: "Alice", age: 30 };
let nameLength = (obj as { name: string }).name.length;
这里手动配
obj
的确切类型为{ name: string }对象
自定义类型
自定义类型
,就是通过文本
的多个组合形式
,来限制
一个值
的可选范围
,包括参数
,以及函数
的返回值
等!
定义常量
类似于在
js
中声明一个const
常量值,且只有一个值
,不能改变值
!
let str: 'hello' = 'hello';
试着去修改
str
let str: 'hello' = 'hello';
str = 'hello2';
提示
报错
信息不能将类型“"hello2"”分配给类型“"hello"”
。
限定类型可选范围
比如在
函数
中控制某一个参数类型的可选范围
!
function setTextDirection(text: string, direction: 'left' | 'right' | 'center'){
console.log(text +'is'+ direction);
}
setTextDirection("hello", "left") // or right / center
setTextDirection("hello", "center")
这里是
设置文本的方向
,我们将第二个方向direction参数
,可选类型范围
规定为(left | right)
,当传参
时,且只能是left
或right
!
定义函数返回值可选范围
function isExist( text: string, str: string) : true | false {
return !(text.indexOf(str) === -1);
}
function isExist( text: string, str: string) : 1 | -1 {
return text.indexOf(str) === -1 ? -1 : 1;
}
let index = isExist("hello is left", 'a');
console.log(index);
以上代码,主要是找出一段
字符串中的某个字符
,若找到
,则返回 true 或 1
,相反则为 false 或 -1
!
请求案例
function request(url: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE', data: any) {
console.log(`Sending ${method} request to ${url} with data: ${data}`);
}
/* let options = {
url: 'https://example.com',
method: 'GET' as 'GET',
data: { name: 'John' }
} */
let options = {
url: 'https://example.com',
method: 'GET',
data: { name: 'John' }
} as const;
/*
// as 'GET' 限定 method 类型为 'GET',options.method 类型为 'string' 范围太大,无法确定 GET 还是 POST 所以不加会报错
// as const 限定 options 类型为 { url: string, method: 'GET', data: any },options.method 类型为 'GET',options.data 类型为 any
*/
request(options.url, options.method, options.data); // Sending GET request to https://example.com with data: { name: 'John' } 'GET', { name: 'John' });
枚举
枚举
在ts
中是通过enum
关键字实现的,主要用来定义一组固定的常量值
,供业务模块
去使用,当ts代码
被编译
后,enum
代码块就不存在了
!
基本使用
enum Color {Red, Green, Blue};
let c: Color = Color.Green;
console.log(c); // output: 1
enum Direction {Up = 1, Down, Left, Right};
let d: Direction = Direction.Right;
console.log(d); // output: 3
使用场景
// 数字枚举
enum Status {
Active,
Inactive,
Pending
}
console.log(Status.Active); // 输出: 0
// 字符串枚举
enum StatusString {
Active = "ACTIVE",
Inactive = "INACTIVE",
Pending = "PENDING"
}
console.log(StatusString.Active); // 输出: "ACTIVE"
// 常量枚举
const enum Direction {
Up,
Down,
Left,
Right
}
let directions = [Direction.Up, Direction.Down];
// 编译后等同于:
// let directions = [0, 1];
// 计算成员
enum BitFlags {
None = 0,
Flag1 = 1 << 0,
Flag2 = 1 << 1,
Flag3 = 1 << 2,
Flag4 = 1 << 3
}
console.log(BitFlags.Flag1); // 输出: 1
console.log(BitFlags.Flag2); // 输出: 2
常量枚举
常量枚举
,是一种特殊的枚举类型
,通过const
来表示此枚举为常量枚举
,其作用就是避免编译时生成过多的js代码
!
// 常量枚举
const enum Direction {
Up,
Down,
Left,
Right
}
let directions = [Direction.Up, Direction.Down];
如果
不使用const常量枚举
时,编译时会生成过多的代码
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 1] = "Up";
Direction[Direction["Down"] = 2] = "Down";
Direction[Direction["Left"] = 3] = "Left";
Direction[Direction["Right"] = 4] = "Right";
})(Direction || (Direction = {}));
;
let d = Direction.Right;
console.log(d); // output: 3
此时若
加上const常量枚举
时生成的代码
let d = 4 /* Direction.Right */;
console.log(d); // output: 3
泛型
泛型
,当类型不确定
时,我们可以通过泛型来动态设置类型的值!
泛型函数
/*
<T> 泛型参数 可以随便写,根据函数调用时,定义的类型为准
*/
function identity<T>(arg: T): T {
console.log(arg);
return arg;
}
identity<string>("hello");
identity<number>(1223);
泛型接口
/* 泛型接口 */
interface GenericIdentityFn<T> {
name: string;
age: number;
extraInfo: T;
}
const identityFn: GenericIdentityFn<string> = {
name: "miao",
age: 20,
extraInfo: "hello"
};
type obj = {
addressInfo: string;
email: number;
};
const identityType: GenericIdentityFn<obj> = {
name: "miao",
age: 20,
extraInfo: {
addressInfo: "beijing",
email: 1234567890
},
};
泛型类
/* 泛型类 */
class GenericNumber<T> {
name: string;
age: number;
extraInfo: T;
constructor(name: string, age: number, extraInfo: T) {
this.name = name;
this.age = age;
this.extraInfo = extraInfo;
}
printExtraInfo() {
console.log("this.extraInfo" + this.extraInfo);
}
}
const gn = new GenericNumber<obj>("张三", 18, identityType.extraInfo);
类声明文件
类型声明文件
的作用
就是支持在.ts文件中引入
其它.js模块导出的属性和方法
!
类型声明的文件名与js的文件名要保持一致!
新建
demo.d.ts
声明文件,以及demo.js
模块文件:demo.d.ts
:declare function add(a: number, b: number): number; declare function subtract(a: number, b: number): number; export {greet, add, subtract};
demo.js
:function add(a, b) { return a + b; } function subtract(a, b) { return a - b; } export { add, subtract };
新建
.ts文件
,并引入demo.js文件
暴露的模块:import { add, subtract } from "./demo.js"; add(1, 2); subtract(1, 2); greet("world", '2022');
装饰器
装饰器
的根本是由函数
实现的,目前装饰器
功能还是实验性功能
,需要开启配置
后才能使用!
{
"experimentalDecorators": true
}
修改
tsconfig.json
即可!
类装饰器
/*
类装饰器
target 是 PersonDecorator 类
Demo Fun 修改Person原型toString方法 返回JSON字符串
*/
function Demo(target: any) {
target.prototype.toString = function(){
return JSON.stringify(this);
}
}
@Demo
class PersonDecorator {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const p = new PersonDecorator('miao', 20);
console.log(p.toString()); // {"name":"miao","age":20}
关于返回值
如果
装饰器函数
内部返回了一个新的class
,那么会替换掉原有的class类
!
function Demo(target: Function) {
return class {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
test(){
console.log('test');
console.log("test");
console.log("test");
}
};
}
@Demo
class PersonDecorator {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
关于构造类型
构造类型
,就是通过new关键字
可以实例化的函数
,平时我们不能通过Function类型
来限制,因为范围太大
,且箭头函数
也是函数
,但是不能作为构造函数去实例化
,所以需要排除Function类型
的使用!
/*
1. new 表示:该类型可以通过new关键字去使用
2. ...args 表示:该类型可以接收任意数量的实参
3. any[] 表示:表示构造器可以接受任何类型的构造参数
4. {} 表示:表示返回类型是一个对象(非null 和 undefined)对象
*/
type Constructor= new (...args: any[]) => {};
function Demo(target: Constructor) {
return class {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
test() {
console.log("test");
console.log("test");
console.log("test");
}
};
}
@Demo
class PersonDecorator {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const p = new PersonDecorator('miao', 20);
console.log(p.toString()); // {"name":"miao","age":20}
也可以
约束静态属性
type Constructor= {
new (...args: any[]): {},
test: string; // 约束一个静态属性
};
@Demo
class PersonDecorator {
name: string;
age: number;
static test: string = "test"; // 静态属性
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
替换被装饰的类
type Constructor1 = new (...args: any[]) => {};
function LogTime<T extends Constructor1>(target: T) {
/* 继承 PersonDecorator1 类 */
return class extends target {
createTime: Date;
constructor(...args: any[]) {
console.time(target.name);
super(...args);
console.timeEnd(target.name);
this.createTime = new Date();
}
getTime() {
console.log(this.createTime);
}
};
}
@LogTime
class PersonDecorator1 {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
/* 添加类接口 进行约束,防止 以下两个新加属性无法被调用 */
interface PersonDecorator1 {
createTime: Date;
getTime(): void;
}
const pd1 = new PersonDecorator1('Miao', 25);
console.log(pd1.getTime());
装饰器工厂
装饰器工厂
,允许我们在调用外部函数时传递参数
,并且内部返回一个装饰器的函数
!
type Constructor2 = new (...args: any[]) => {};
interface PersonFactory {
introduce(): void;
}
function LogInfo(n: number) {
return function(target: Constructor2) {
target.prototype.introduce = function(){
for(let i = 0; i < n; i++) {
// console.log(`第${i + 1}次调用: ${this.speck() }`);
this.speck();
}
};
};
}
@LogInfo(5)
class PersonFactory {
constructor(public name: string, public age: number) {}
speck() {
console.log(`你好啊 我是${this.name} 今年${this.age}岁了`);
}
}
const pf = new PersonFactory("小明", 20);
pf.introduce(); // 第1次调用PersonFactory.speck()方法 第2次调用PersonFactory.speck()方法 第3次调用PersonFactory.speck()方法 第4次调用PersonFactory.speck()方法 第5次调用PersonFactory.speck()方法
装饰器组合
装饰器组合
,就是多个装饰器
在一个被装饰的类
或者方法
上使用多个装饰器
,其顺序不同
,先从上往下
执行装饰器工厂
,其次在从下往上执行
!
type ConstructorC = new (...args: any[]) => {};
function test1(target: ConstructorC) {
console.log("test1");
}
function test2() {
console.log("test2 工厂");
return function (target: ConstructorC) {
console.log("test2");
};
}
function test3() {
console.log("test3 工厂");
return function (target: ConstructorC) {
console.log("test3");
};
}
function test4(target: ConstructorC) {
console.log("test4");
}
@test1
@test2()
@test3()
@test4
class PersonCombination {}
执行结果
test2 工厂
test3 工厂
test4
test3
test2
test1
属性装饰器
对
属性
进行拦截
,可以监听属性值
的变化
,在变化
时可以处理一些操作
,比如依赖收集
,触发更新
等相关操作!
/*
使用属性装饰器来监听属性值的变化
静态属性 target 对应的是类
实例属性 target 对应的是实例的原型对象
*/
function State(target: object, prototypeKey: string) {
let key: string = `__${prototypeKey}`;
console.log(typeof target, prototypeKey, key);
Object.defineProperty(target, prototypeKey, {
get() {
return this[key];
},
set(newValue: any) {
// 监听 prototypeKey 属性值的变化
// console.log("watch newValue", newValue);
// console.log("target", this, prototypeKey, key);
this[key] = newValue;
},
});
}
class PersonAttr {
name: string;
@State age: number; // 对应的实力的 原型对象
@State static address: string; // 对应的 类
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const pa = new PersonAttr("Miao", 25);
pa.age = 4;
console.log("pa: " + pa.age);
const pa1 = new PersonAttr("姜薇", 35);
pa1.age = 5;
console.log("pa1: " + pa1.age);
以上代码,用来
监听属性的值的变化
!
方法装饰器
可以
对方法执行操作
进行拦截
,在执行前
或函数执行后
打印输出
内容!
/*
实例方法: target 为 类的原型对象
静态方法: target 为 类本身
prototypeKey: 当前方法的名字 【speck】
descriptor: 当前方法的描述符
Logger 在方法执行之前打印 日志
方法执行完毕后再次打印 日志
*/
function Logger(
target: object,
prototypeKey: string,
descriptor: PropertyDescriptor
) {
console.log(`target: ${target}`);
console.log(`prototypeKey: ${prototypeKey} `);
console.log(`descriptor: ${descriptor}`);
// 原方法 descriptor.value PersonFun类的原型对象上speck方法的描述符
const originalMethod = descriptor.value;
// 重写方法 当实例调用该方法是并传入参数时,通过call 或者 apply 调用原方法,并打印日志
descriptor.value = function (...args: any[]) {
console.log(`调用 ${prototypeKey} 方法 之前执行....`);
let result = originalMethod.apply(this, args);
console.log(`调用 ${prototypeKey} 方法 之后执行....`);
return result;
};
console.log("装饰器函数执行了!");
}
class PersonFun {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
@Logger
speck() {
console.log("我是" + this.name + ",今年" + this.age + "岁了!");
}
// 静态方法
static sayHello() {}
}
const pf1 = new PersonFun("小明", 20);
pf1.speck(); // 调用 speck 方法 之前执行.... 调用 speck 方法 之后执行.... 我是小明,今年20岁了!
访问装饰器
访问装饰器
,就是一个类
内部中的get
和set
方法,当一个类
中有一个私有的方法或属性
时,则其无法访问该私有属性
或方法
,若想访问
时,可以通过访问器
的方式来提供外部使用
!
/*
装饰器访问器
*/
function Range1( min: number, max: number ){
return function (target: object, propertyKey: string, descriptor: PropertyDescriptor){
// 重写 setter
const originalSetter = descriptor.set;
descriptor.set = function ( value: number){
// 判断 value 是否在 min 和 max 之间
if( value < min || value > max ){
throw new Error(`Value must be between ${min} and ${max}`);
}
originalSetter?.call(this, value);
};
}
}
class Wetaher {
private _temperature: number = 0;
constructor(temperature: number) {
this._temperature = temperature;
}
@Range1(0, 100)
set temperature(value: number) {
this._temperature = value;
}
get temperature() {
return this._temperature;
}
}
const w = new Wetaher(25);
// w.temperature = -10; // 访问器生效,设置属性值
w.temperature = 54;
console.log(w.temperature); // 访问器生效,获取属性值,输出 30
参数装饰器
参数装饰器
,我们可以通过装饰器
来拦截参数
,对参数
进行一些逻辑操作
,需要关注的是,参数对应
的方法
是实例方法
时,则target
对应的是类的原型对象
,若是static静态方法
时,则返回的是类的本身
!
需要配合 方法装饰器来一起使用!
/*
装饰器参数
target: 当前类的原型对象 如果是实例方法,则 target 为 类的原型对象,若是静态方法,则 target 为 类本身
propertyKey: 当前属性的名字
parameterIndex: 当前参数在列表中的索引
*/
function Params( target: any, propertyKey: string, parameterIndex: number ){
console.log( `target: ${target}, propertyKey: ${propertyKey}, parameterIndex: ${parameterIndex}` );
}
class Classision {
className: string;
classNo: number;
students: string[];
course: string[] = ['数学', '语文', '英语'];
constructor(className: string, classNo: number, students: string[], course?: string[]) {
this.className = className;
this.classNo = classNo;
this.students = students;
}
addCourse( @Params course: string ) {
this.course.push(course);
}
}
const ci = new Classision('101', 1, ['小明', '小红']);
ci.addCourse('物理'); // target: [Function: Classision], propertyKey: addCourse, parameterIndex: 0
console.log(":ci", ci);