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.jstsc --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);
