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