TypeScript 的用法

一、类型声明

TypeScript 复制代码
// 变量类型声明
let name: string = "张三";
let age: number = 25;
let isStudent: boolean = true;
let hobbies: string[] = ["篮球", "阅读", "编程"];
let scores: number[] = [95, 88, 92];

// 对象类型声明
let person: {
    name: string;
    age: number;
    email?: string;  // 可选属性
} = {
    name: "李四",
    age: 30
};

// 数组类型声明(多种写法)
let numbers: Array<number> = [1, 2, 3];
let matrix: number[][] = [[1, 2], [3, 4]];

// 元组类型(固定长度和类型)
let tuple: [string, number] = ["王五", 28];

// 联合类型
let id: string | number;
id = "ABC123";
id = 123;

// 函数参数和返回值类型声明
function add(x: number, y: number): number {
    return x + y;
}

// 箭头函数类型声明
const multiply = (a: number, b: number): number => a * b;

// 接口声明
interface User {
    id: number;
    name: string;
    age?: number;
}

let user: User = {
    id: 1,
    name: "小明"
};

// 类型别名
type Point = {
    x: number;
    y: number;
};

const point: Point = { x: 10, y: 20 };

// 枚举声明
enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT"
}

let move: Direction = Direction.Up;

二、类型推断

TypeScript 复制代码
// ========== 基础类型推断 ==========

// 变量初始化推断
let name = "张三";        // TypeScript 推断为 string
let age = 25;            // 推断为 number
let isActive = true;     // 推断为 boolean
let scores = [90, 85, 95]; // 推断为 number[]

// 自动推断示例
let items = ["苹果", "香蕉", "橙子"];  // 推断为 string[]
items.push("葡萄");      // ✓ 正确
// items.push(123);      // ✗ 错误:类型"number"的参数不能赋给类型"string"的参数

// ========== 最佳通用类型推断 ==========

// 混合类型数组推断
let mixedArray = [1, "hello", true];  // 推断为 (string | number | boolean)[]

// ========== 上下文类型推断 ==========

// 事件处理函数推断
document.addEventListener("click", function(event) {
    console.log(event.clientX, event.clientY);  // event 被推断为 MouseEvent
});

// ========== 函数返回类型推断 ==========

// 函数返回值推断
function add(a: number, b: number) {
    return a + b;  // TypeScript 推断返回值为 number
}

const result = add(5, 3);  // result 被推断为 number

function createUser(name: string, age: number) {
    return {
        name,  // 推断返回类型为 { name: string; age: number }
        age
    };
}

const user = createUser("小红", 25);  // user 被推断为 { name: string; age: number }

// ========== 对象属性推断 ==========

const employee = {
    name: "张三",
    age: 30,
    department: "技术部",
    skills: ["TypeScript", "React", "Node.js"]  // 推断为 string[]
};

// TypeScript 推断 employee 的类型为:
// {
//     name: string;
//     age: number;
//     department: string;
//     skills: string[];
// }

// ========== 解构推断 ==========

const company = {
    companyName: "ABC科技",
    location: "北京",
    employees: 500
};

const { companyName, location } = company;  
// companyName 推断为 string
// location 推断为 string

// ========== 数组方法推断 ==========

const numbers = [1, 2, 3, 4, 5];

// map 方法推断
const doubled = numbers.map(n => n * 2);  // 推断为 number[]

// filter 方法推断
const evens = numbers.filter(n => n % 2 === 0);  // 推断为 number[]

// ========== 条件语句中的推断 ==========

let value: string | number = "hello";

if (typeof value === "string") {
    console.log(value.toUpperCase());  // 在此块中,value 被推断为 string
} else {
    console.log(value.toFixed(2));     // 在此块中,value 被推断为 number
}

// ========== 类型断言与推断 ==========

// 有时需要帮助 TypeScript 进行推断
const input = document.getElementById("username");

// 如果没有类型断言,input 被推断为 HTMLElement | null
if (input) {
    // 使用类型断言帮助推断
    (input as HTMLInputElement).value = "张三";
}

// ========== 更复杂的推断案例 ==========

// 1. 数组映射推断
interface Product {
    id: number;
    name: string;
    price: number;
}

const products: Product[] = [
    { id: 1, name: "笔记本", price: 5000 },
    { id: 2, name: "鼠标", price: 100 }
];

// 自动推断为 number[]
const productPrices = products.map(product => product.price);

// 2. 异步函数推断
async function fetchData() {
    const response = await fetch("https://api.example.com/data");
    const data = await response.json();  // 推断为 any
    return data;
}

// 3. 类中的推断
class Calculator {
    private currentValue = 0;  // 推断为 number
    
    add(num: number) {
        this.currentValue += num;  // TypeScript 知道这是数字运算
        return this.currentValue;  // 推断返回 number
    }
}

// ========== 实际应用示例 ==========

// 案例:处理用户数据
interface User {
    id: number;
    name: string;
    email: string;
}

const users: User[] = [
    { id: 1, name: "张三", email: "zhangsan@example.com" },
    { id: 2, name: "李四", email: "lisi@example.com" }
];

// TypeScript 能推断出:
// - user 的类型是 User
// - userNames 的类型是 string[]
const userNames = users.map(user => user.name);

// TypeScript 能推断出:
// - activeUsers 的类型是 User[]
// - 回调参数 user 的类型是 User
const activeUsers = users.filter(user => user.id > 0);

// 案例:配置对象推断
const config = {
    apiUrl: "https://api.example.com",
    timeout: 5000,
    retry: true,
    headers: {
        "Content-Type": "application/json"
    }
};

// config 被完整推断,无需显式类型声明
console.log(config.headers["Content-Type"]);  // 正确推断

三、类型总览

(1)Javascript 中的数据类型

string、number、boolean、null、undefined、bigint、symbol、object

object 包含:Array、Function、Date、Error

(2)TypeScript 中的数据类型

上述所有的 JavaScript 类型

六个新类型:any、unknown、never、void、tuple、enum

两个用于自定义类型的方式:type、interface

四、常用类型与语法

(1) any

any 是 TypeScript 中最灵活的类型,它禁用了类型检查。

TypeScript 复制代码
// 1. 基本用法
let value: any;

value = 42;           // ✓ 可以赋值为数字
console.log(value);   // 42

value = "hello";      // ✓ 可以赋值为字符串
console.log(value.toUpperCase());  // HELLO

value = true;         // ✓ 可以赋值为布尔值
value = [1, 2, 3];    // ✓ 可以赋值为数组
value = { name: "张三" }; // ✓ 可以赋值为对象

// 2. any 绕过了类型检查
let num: number = 10;
let anyValue: any = "我是一个字符串";

num = anyValue;  // ✓ 不会报错,但运行时可能出错
console.log(num.toFixed(2));  // 运行时错误!

// 3. 函数中的 any
function processData(data: any) {
    // 在这里可以对 data 做任何操作
    console.log(data.name);
    console.log(data.age);
    return data;
}

// 可以传入任何类型的参数
processData("字符串");
processData(123);
processData({ name: "张三" });
processData([1, 2, 3]);

// 4. any 数组
let mixedArray: any[] = [1, "hello", true, { name: "test" }];
mixedArray.push(100);      // ✓
mixedArray.push("world");  // ✓

// 5. 隐式的 any
// 当没有类型注解且 TypeScript 无法推断类型时,会隐式推断为 any
let implicitAny;  // 类型为 any
implicitAny = 10;
implicitAny = "string";

// 开启 strict 模式时,这会被禁止

为什么不推荐使用 any

TypeScript 复制代码
// 使用 any 会失去 TypeScript 的优势
let userId: any = "user123";

// TypeScript 不会报错,但这是错误的用法
userId.toFixed(2);    // ✗ 运行时错误:字符串没有 toFixed 方法

// 对比:使用具体类型
let userId2: string = "user123";
// userId2.toFixed(2);  // ✓ TypeScript 会立即报错,提示你错误

// 更好的做法:使用联合类型
let userId3: string | number = "user123";
if (typeof userId3 === "number") {
    userId3.toFixed(2);  // ✓ 安全,有类型保护
}

何时使用 any

迁移 JavaScript 项目到 TypeScript

处理第三方库没有类型定义的情况

暂时绕过复杂的类型问题

TypeScript 复制代码
// 实际例子:处理未知的 API 响应
async function fetchData() {
    const response = await fetch('https://api.example.com/data');
    const data: any = await response.json();  // 暂时使用 any
    return data;
}

// 稍后可以优化为具体类型
interface ApiResponse {
    id: number;
    name: string;
    // ...
}

async function fetchDataBetter() {
    const response = await fetch('https://api.example.com/data');
    const data: ApiResponse = await response.json();
    return data;
}

替代 any 的方案

TypeScript 复制代码
// 1. unknown - 比 any 更安全
let value: unknown;
value = 10;
value = "hello";

// 需要类型检查后才能使用
if (typeof value === "string") {
    console.log(value.toUpperCase());  // ✓ 安全
}

// 2. 类型断言
let someValue: any = "hello";
let strLength: number = (someValue as string).length;

// 3. 泛型 - 保持类型安全
function identity<T>(arg: T): T {
    return arg;
}

// 4. 类型保护
function isString(value: any): value is string {
    return typeof value === "string";
}

any 是 TypeScript 的"逃生舱",可以暂时绕过类型检查,但应该尽量避免使用,因为会失去类型安全的优势。

(2) unknown

1.可以理解为一个类型安全的any,适用于:不确定数据的具体类型

2.会强制开发者在使用之前进行类型检查从而提供更强的类型安全性

TypeScript 复制代码
// 设置 a 的类型为 unknown

let a : unknown
a = 99
a = false
a = 'hello'

console.log(a)

let x : string

// 第一种:加类型判断
if ( typeof a === 'string'){
  x = a
}

// 第二种:加断言
x = a as string

// 第三种:加断言
x = <string>a

3.读取 any 类型数的任何属性都不会报错,而 unknown 正好相反

TypeScript 复制代码
let str1: string
str1 = 'hello'
str1.toUpperCase()                       // 无警告

let str2: any
str2 = 'hello'
str2.toUpperCase()                       // 无警告

let str3: unknown
str3 = 'hello'
str3.toUpperCase()                       // 警告:'str3' 的类型为 '未知'

// 使用断言强制指定 str3 的类型为 string
(str3 as string).toUpperCase()           // 无警告

(3) never

含义:任何值都不是,简言之就是不能有值,undefined、null、''、0都不行

1.几乎不用 never 去直接限制变量,因为没有意义,

  1. never 一般是 TypeScript 主动推断出来的
TypeScript 复制代码
// 指定 a 的类型为 never, 那就意味着 a 以后不能存任何的数据了 

let a: never

// 以下对 a 的所有赋值都会有警告
a = 1
a = true
a = undefined
a = null
  1. never 一般是 TypeScript 主动推断出来的
TypeScript 复制代码
// 指定 a 的类型为 string
let a: string
// 给 a 设置一个值
a = 'hello'

if (typeof a === 'string'){
  console.log(a.toUpperCase())
} else {
  console.log(a)  // TypeScript 会推断出此处的 a 是 never,因为没有任何一个值符合此处的逻辑
}
  1. never 也可用于限制函数的返回值
TypeScript 复制代码
// 限制 throwError 函数不需要有任何返回值,任何值都不行,像 undefined、null 都不行
function throwError(str: string): never {
  throw new Error ('程序异常退出' + str)
}

(4) void

通常用于函数返回值声明,含义:【函数不返回任何值,调用者也不应该依赖其返回值进行任何操作】

TypeScript 复制代码
function logMessage(msg:string):void{
  console.log(msg)
}
logMessage('Hello')

注意:编码者没有编写 return 去指定函数的返回值,所以 logMessage 函数是没有显示返回值的,但会有一个隐式返回值,就是 undefined;即:虽然函数返回类型为 void,但也是可以接受 undefined 的,简单记:undefined 是 void 可以接受的一种 '空'。

  1. 以下写法均符合规范
TypeScript 复制代码
// 无警告
function logMessage(msg:string):void{
  console.log(msg)
}

// 无警告
function logMessage(msg:string):void{
  console.log(msg)
  return
}

// 无警告
function logMessage(msg:string):void{
  console.log(msg)
  return undefined
}

理解 void 和 undefined

void 是一个广泛的概念,用来表达'空', 而 undefined 则是这种空的具体实现之一。

因此可以说 undefined 是 void 能接受的 '空' 状态的一种具体形式。

换句话说:void 包含 undefined,但 void 表达的语义超越了单纯的 undefined,它是一种意图上的约定,而不仅仅是特定的限制。
总结:若函数返回类型为 void, 那么:

  1. 从语法上讲:函数是可以返回 undefined 的,至于显示返回,还是隐式返回,这无所谓

  2. 从语义上讲:函数调用者不应关心函数返回的值,也不应依赖返回值进行任何操作,即使返回了 undefined 值

(5) object

关于 object 与 Object,实际开发中用的相对较少,因为范围太大了。

1. object

所有非原始类型,可存储:对象、函数、数组等,由于限制的范围别叫宽泛,在实际开发中使用的相对较少。

TypeScript 复制代码
let a: object           // a 能存储的类型是【非原始类型】
let a: Object           // B 能存储的类型是可以调用到 Object 方法的类型


// // 以下代码,是将【非原始类型】赋给a,无警告
a = {}
a = {neme:'tom'}
a = [1,3,5,7,9]
a = fuction(){}
a = new String('123')
class Person {}
a = new Person{}

// 以下代码,是将【原始类型】赋给a,有警告
a = 1                     //  警告:不能将类型 'number' 分配给类型 'object'
a = true                  //  警告:不能将类型 'boolean' 分配给类型 'object'
a = 'Hello'               //  警告:不能将类型 'string' 分配给类型 'object'
a = null                  //  警告:不能将类型 'null' 分配给类型 'object'
a = nudefined             //  警告:不能将类型 'undefined' 分配给类型 'object'
2. Object

官方描述:所有可以调用的 Object 方法的类型

简单记忆:除了 undefined 和 null 的任何值

由于限制的范围实在太大了,所以实际开发中使用率极低

TypeScript 复制代码
b = {}
b = {neme:'tom'}
b = [1,3,5,7,9]
b = fuction(){}
b = new String('123')
class Person {}
b = new Person{}
b = 1
b = true
b = 'Hello'

b = null                 //  警告:不能将类型 'null' 分配给类型 'object'
b = undefined            //  警告:不能将类型 'undefined' 分配给类型 'object'
3. 声明对象类型
  1. 实际开发中, 限制一般对象,通常使用以下形式
TypeScript 复制代码
// 限制 person1 对象必须有 name 属性,age 为可选属性
let person1:{name: string, age?: number}

// 含义同上, 也能用分号做分割
let person2: {name: string; age?: number}

// 含义同上, 也能用分号做分割
let person3: {
  name: string
  age?: number
}

// 以下赋值均可以
person1 = {name:'李四', age:18}
person2 = {name:'张三'}
person3 = {name:'王五'}

// 以下赋值不合法,因为 person3 的类型限制中,没有对 gender 属性的说明
person3 = {name:'王五', gender:'男'}

2.索引签名:允许定义对象可以具有任意数量的属性,这些属性的键和类型是可变的,常用于:描述类型不确定的属性。(具有动态属性的对象)

TypeScript 复制代码
// 限制 person 对象必须有 name 属性, 可选 age 属性但值必须是数字,同时可以有任意数量、任意类
let person{
  name: string
  age?: number
  [key: sting]: any    //索引签名,完全可以不用 key 这个单词,换成其他可也可以
}

// 赋值合法
person  = {
  name:'张三'
  按个: 18
  gender: '男'
}
4. 声明函数类型
TypeScript 复制代码
let count:(a: number, b: number) => number

count = function(x,y){
  return x + y
}

备注:

typeScript 中的 => 在函数类型声明时表示函数类型,描述其参数类型和返回类型。

JavaScript 中的 => 是一种定义函数的语法,是具体的函数实现

函数类型声明还可以使用:接口、自定义类型等方式,下文中会详细讲解

5. 声明数组类型
TypeScript 复制代码
let arr1: string[]
let arr2: Array<string>

arr1 = ['a','b','c']
arr2 = ['Hello','World']

// 上述代码中的 Array<string> 属于泛型,下文会讲解

(6) tuple

元组(Tuple)是一种特殊的数组类型,可以存储固定数量的元素,并且每个元素的类型是已知的且可以不同。元组用于精确描述一组值得类型,?表示可选元素

TypeScript 复制代码
// 第一个元素必须是 string 类型,第二个元素必须是 number 类型。
let arr1: [strig, number]

// 第一个元素必须是 number类型,第二个元素是可选的,如果存在,必须是 boolean 类型。
let arr1: [number, boolean?]

// 第一个元素必须是 number类型,后面得元素可以是任意数量的 string 类型
let arr1: [strig, ...string[]]

// 可以赋值
arr1 = ['Hello', 123]
arr2 = [100, false]
arr2 = [200]
arr3 = [100, 'Hello', 'World']
arr3 = [100]

// 不可以赋值, arr1 声明时是两个元素,赋值的是三个
arr1 = ['Hello', 123, false]

(7) enum

枚举(enum)可以定义一组命名常量,它能增强代码的可读性,也让代码更好维护

以下代码的功能是:根据调用 walk 时传入的不同参数,执行不同的逻辑,存在的问题时调用 walk 时传参时没有任何提示,编码者很容易写错字符串内容;并且用于判断逻辑的 up、down、left、right 是连续且相关的一组值,那此时九特别适合使用枚举(enum)

TypeScript 复制代码
function walk(str:string){
  if (str === 'up'){
    console.log('向上走')
  } else if (str === 'down'){
    console.log('向下走')
  } else if (str === 'left'){
    console.log('向左走')
  } else if (str === 'right'){
    console.log('向右走')
  } else {
    console.log('未知方向')
  }
}

walk('up')
walk('down')
walk('left')
walk('right')
1.数字枚举

常见的枚举类型,成员的值会自动递增 ,且数字枚举还具备反向映射的特点。

TypeScript 复制代码
// 定义一个描述【上下左右】方向的枚举 Direction

enum Directio{
  up,
  Down,
  Left,
  Right
}

console.log(Direction)  // 打印后会显示以下内容

/*
  0:'Up',
  1:'Up',
  2:'Left',
  3:'Right',
  Up:0,
  Down:'1,
  Left:'2,
  Right:3
*/

// 反向映射
console.log(Direction.Up)
console.log(Dirction.[0])

// 此行代码报错,枚举中的属性是只读的
Direction.Up = 'shang'

也可以指定枚举成员的初始值,其后的成员值会自动递增

TypeScript 复制代码
enum Direction{
  up = 6,
  Down,
  Left,
  Right
}

console.log(Direction.Up)  // 输出:6
console.log(Direction.Down)  // 输出:7

使用数字枚举完成刚才的 walk 函数中的逻辑,代码更加直观易读,而且类型安全,也易维护

TypeScript 复制代码
enum Directio{
  up,
  Down,
  Left,
  Right
}

function walk(n: Direction){
  if (n === Direction.Up){
    console.log('向上走')
  } else if ((n === Direction.Down){
    console.log('向下走')
  } else if ((n === Direction.Left){
    console.log('向左走')
  } else if ((n === Direction.right){
    console.log('向右走')
  } else {
    console.log('未知方向')
  }
}

walk.(Direction.Up)
walk.(Direction.Down)
walk.(Direction.Left)
walk.(Direction.Right)
2. 字符串枚举
TypeScript 复制代码
enum Directio{
  Up = 'Up',
  Down = 'Down',
  Left = 'Left',
  Right = 'Right'
}

let dir:Direction = Direaction.Up;
console.log(dir); // 输出:'Up'
3. 常量枚举

官方描述:是一种特殊枚举类型,它使用 const 关键字定义,在编译时会被内联避免 生成一些额外的代码
所谓"内联"就是 TypeScript 在编译时,会将枚举成员引用替换为它们的实际值,而不是生成额外的枚举对象,这可以减少生成的 JavaScript 代码量,并提高运行时性能

使用普通枚举的 Typescript 代码如下:

TypeScript 复制代码
enum Directio{
  up,
  Down,
  Left,
  Right
}

console.log(Direction.Up)

// 编译后的 JavaScript 代码量较大

使用常量枚举的 TypeScript 代码如下:

TypeScript 复制代码
const enum Directio{
  up,
  Down,
  Left,
  Right
}

let x = Direction.Up

// 编译后的 Javascript 代码量较小

"use strict"
let x = 0  /* Direction.Up */

(8) type

type 可以为任意类型创建别名,让代码更简洁、可读性更强,同时能更方便地进行类型复用和扩展

1.基本用法

类型别名使用 type 关键字定义,type 后跟类型名称,例如下面代码中 num 是类型别名

TypeScript 复制代码
type num = number

let price: num
price = 100
  1. 联合类型

是一种高级类型,它表示一个值可以是几种不同类型之一。

TypeScript 复制代码
type Status = number | string
type Gender = '男' | '女'

function printStatus(status: Status){
  console.log(status)
}

function logGender(str: Gender){
  console.log(str)
}

printStatus(404)
printStatus(200)
printStatus(501)

logGender('男')
logGender('女')
  1. 交叉类型

交叉类型(Itersection Types)允许将多个类型合并为一个类型。合并后的类型将拥有所有被合并类型的成员。交叉类型通常用于对象类型。

TypeScript 复制代码
// 面积
type = Area = {
  herght: number
  width: number
}

// 地址
type = Address = {
  num: number
  cell: number
  room: string
}

type House = Area & Address

const house: House = {
  height: 180
  weight: 75
  num: 6
  cell: 3
  room: '316'
}

(9) 一个特殊情况

代码1(正常)

再函数定义时,限制函数返回值为 void,那么函数的返回值就必须是空。

TypeScript 复制代码
function demo():void{
  // 返回 undefined 合法
  return undefined

  // 以下返回均不合法
  return 100
  return false
  return null
  return []
}

demo()

代码2(特殊)

使用类型声明限制函数返回值 void 时, TypeScript 并不会严格要求函数返回空。

TypeScript 复制代码
type LogFunc = () => void

const f1: LogFunc = () => {
  return 100                       // 允许返回非空值
}

const f2: LogFunc = () => 200      // 允许返回非空值

cosnt f3: LogFunc = function () {
  return 300                       // 允许返回非空值
}

为什么会这样?

是为了确保如下代码成立,我们知道 Array.prototype.push 的返回一个数字,而 Array。prototype.forEach 的方法期望其回调的返回类型是 void

TypeScript 复制代码
const src = [1, 2, 3]
const dst = [0]

src.forEach((el) => dst.push(el))

(10) 类相关知识

创建类

TypeScript 复制代码
class Person{
  // 属性声明
  name: string
  age: number
  // 构造器
  constructor(name: string, age:number) {
    this.name = name
    this.age = age
  }
  // 方法
  speak(){
    console.log((`姓名:${this.name}, 年龄:${this.age}`))
  }
}

// Person 实例
const p1 = new Person('小明', 12)
p1()

类继承

TypeScript 复制代码
class Student extends Person{
  grade: string
  // 构造器
  constructor(name: sting, age: number, grade: string){
    super(name, age)
    this.grade = grade
  }
  // 备注本例中 Student 类不需要额外的属性, Student 的构造器可以忽略
  // 重写从父类继承的方法
  override speak(){
     console.log((`姓名:${this.name}, 年龄: ${this.age}, 年级: $(this.grade)`))
  }
  // 子类自己的方法
  study(){
    console.log(`$(this.name)正在努力学习中...`)
  }
}

(11) 属性修饰符

修饰符 含义 具体规则
public 公开的 可以被:类内部、子类、类外部访问
protected 受保护的 可以被:类内部子类访问
private 私有的 可以被:类内部访问
readonly 只读属性 属性无法修改
1. public 修饰符
TypeScript 复制代码
class Person{
  // name 写了 public 修饰符, age 没写修饰符, 最终都是 public 修饰符
  public name: string
  age: number
  // 构造器
  constructor(name: string, age:number) {
    this.name = name
    this.age = age
  }
  // 方法
  speak(){
    // 类的【内部】可以访问 public 修饰符的 name 和 age
    console.log((`姓名:${this.name}, 年龄:${this.age}`))
  }
}

// Person 实例
const p1 = new Person('小明', 12)
// 类的【外部】可以访问 public 修饰的属性
console.log(p1.name)

class Student extends Person{
  // 构造器
  constructor(name: sting, age: number, grade: string){
    super(name, age)
    
  }
  study(){
    // 【子类中】可以访问父类中 public 修饰的: name属性、age属性
    console.log(`$(this.name)正在努力学习中...`)
  }
}
2. 属性简写形式
TypeScript 复制代码
// 简写前

class Person{
  // name 写了 public 修饰符, age 没写修饰符, 最终都是 public 修饰符
  public name: string
  age: number
  // 构造器
  constructor(name: string, age:number) {
    this.name = name
    this.age = age
  }
TypeScript 复制代码
// 简写后

class Person {
  constructor(
    public name:string,
    public age: number
  ) {}
}
3. protected 修饰符
TypeScript 复制代码
class Person{
  // name 和 age 是受保护属性,不能再类外部访问,但可以在【类】与【子类】中访问
  constructor(
    protected name: string
    protected age: number
  )
  // getDetails 是受保护方法,不能在类外部访问,但可以在【类】与【子类】中访问
  protected getDetails(): string{
    // 类中能访问受保护的 name 和 age 属性
    return `name: ${this.name}, 年龄: ${this.age}`
  }
  // introduce 是公开方法,类、子类、类外部都能使用
  introduce(){
    // 类中能访问受保护的 getDetails 方法
    console.log(this.getDetails())
  } 
}

const p1 = new Person('张三', 18)

// 可以在类外部访问 introduce
p1.introduce()

p1.name          // 报错,类的外部不能访问
p1.age           // 报错,类的外部不能访问
p1.getDetails    // 报错,类的外部不能访问

private 修饰符

TypeScript 复制代码
class Person{
  constructor(
    public name: string
    public age:number
    private IDCard: string
  ) { }
}
private getPrivateInfo(){
  return `身份证号码: $(this.IDcard)`
}

getInfo(){
  retun `姓名: ${this.name}, 年龄: $(this.age)`
}

getFullInfo(){
  return this.getInfo() + ', '+ this.getPrivateInfo()
}

const p1 = new Person('tom', 18, '210540198606271234')

p1.name                            // 可访问
p1.age                             // 可访问
p1.IDCard                          // 不可访问

console.log(p1.getInfo())          // 可打印
console.log(p1.getFullInfo())      // 可打印

p1.getPrivateInfo()                // 不能调用

readonly 修饰符

TypeScript 复制代码
class Car{
  constructor(
    public readonly vin: string    // 车辆识别码,只读属性
    public readonly year: number   // 出厂年份, 只读属性
    public color: string
    public sound: string
  ) { }
}

// 打印车辆信息
displayInfo() {
  console.log(`
    识别码: ${this.vin}
    出厂年份: ${this.year}
    颜色: ${this.color}
    音响: ${this.sound}
  `)
}

const car = new Car('2HRSLJ2354592445', 2020, '黑色', 'Bose')
car.displayInfo()

// 以下代码均为错误: 不能修改 readonly 属性
car.vin = '3tHGFJOGMN873y423423'
car.year = 2022

(12) 抽象类

  • 概述:抽象类是一种无法被实例化的类,专门用来定义类的结构和行为,类中可以写抽象方法,也可以写具体实现。抽象类主要用来为其派生类提供一个基础结构, 要求其派生类必须实现其中的抽象方法。
  • 简记:抽象类不能实例化,其意义是可以被继承,抽象类里可以有普通方法、也可以有抽象方法

通过以下场景,理解抽象类:

定义一个抽象类 Package,表示所有包裹的基本结构,任何包裹都有重量属性 weight,包裹都需要计算运费。但不同类型的包裹(如:标准速度、特快转递)都有不同的运费计算方式,因此用于计算运费 calculate 方法是一个抽像方法,必须由具体的子类来实现

Package 类

TypeScript 复制代码
abstract class Package{
  // 构造方法
  constructor(public weight:number){}
  //抽象方法
  constract calculate(): number
  //具体方法
  printPackage(){
    console.log(`包裹重量为:${this.weight}kg, 运费为:${this.calculate()}元`)
  }
}

class StandardPackage extends Package{
  constructor(
    weight: number
    public unitPrice: number
  ){ super(weight)}
  calculate(): number{
    return this.weiht * this.unitPrice
  }
}

class ExpressPackage extends Package{
  constructor(
    weight: number
    public unitPrice: number
    public additional: number
  ) {supre(weight)}
  calculate(): number{
    if(this.weight > 10){
      return 10 * this.unitPrice + (this.weight - 10) * this.additional
    } else {
      return this.weight * this.unitPrice
    }
    }
  }
}

StandardPackage 类继承了Package,实现了 calculate 方法:

expressPackage 类继承了Package,实现了calculateAmount 方法:

总结:何时使用抽象类

  1. 定义通用接口: 为一组相关的类定义通用的行为(方法或属性)时

  2. 提供基础实现:在抽象类中提供某些方法或为其提供基础实现,这样派生类就可以继承这些实现

  3. 确保关键实现: 强制派生类实现一些关键行为。

4.共享代码和逻辑:当多个类需要共享部分代码时,抽象类可以避免代码重复。

(13) interface (接口)

interface 是一种定义结构 的方式,主要作用是为:类、对象、函数等规定一种契约,这样可以确保代码的一致性和类型安全,但要注意 interface 只能定义格式,不能包含任何实现

1. 定义结构
TypeScript 复制代码
// PersonInterface 接口, 用于限制 Person 类的格式
interface PersonInterface{
  name: string
  age: number
  speak(n: number): void
}

// 定义一个类 Person, 实现 PersonInterface 接口
class Person implements PersonInterface{
  constructor(
    public name: string
    public age: number
  ){ }
  // 实现接口中的 speak 方法, 注意:实现 speek 时参数个数可以少于接口中的规定, 但不能多
  speak(n: number): void{
    for(let i=0; i<n; i++){
      // 打印出包含名字和年龄的问候语句
      console.log(`姓名:$(this.name), 年龄:${this.age}`)
    }
  }
}

// 创建一个 Person 类的实例 p1, 传入名字 'tom' 和年龄 18
const p1 = new Person('tom', 18)
p1.speak(3)

// 打印结果如下
// 姓名:tom, 18
// 姓名:tom, 18
// 姓名:tom, 18
2. 定义对象结构
TypeScript 复制代码
interface UserInterface{
  name: sting
  readonly gender: sting   // 只读属性
  age?: number             // 可选属性
  run: (n: number) => void
}

cosnt user: UserInterface = {
  name: '张三'
  gender: '男'
  age:18
  run(n) {
    console.log(`奔跑了${n}米`)
  }
}
3. 定义函数结构
TypeScript 复制代码
interface CountInterface{''
  (a: number, b: number): number
}

const count: CountInterface = (x, y) =>{
  return x + y
}
4. 接口之间的继承
TypeScript 复制代码
interface PersonInterface {
  name: string
  age: number  
}

interface StudentInterface extends PersonInterface {'

}
5. 接口自动合并(可重复定义)
TypeScript 复制代码
interface PersonInterface {
  name: string
  age: number  
}

interface PersonInterface {
  gender: stinng
}

 const p: PersonInterface = {
   name:'tom'
   age = 18
   gender: '男'
}
总结:何时使用接口?
  1. 定义对象的格式:描述数据模型、API 响应格式、配置对象......等等,是开发中最常用的

  2. 类的契约:规定一个类需要实现哪些属性和方法。

  3. 自动合并:一般用于扩展第三方库的类型,这种特性在大型项目中可能会用到。

(14) 一些相似概念的区别

1. interface 与 type 的区别

相同点:interface 和 type 都可以用于定义对象结构,两者在许多场景中时可以互换的

不同点:interface:更专注于定义对象和类的结构,支持继承、合并。

type:可以定义类型别名、联合类型、交叉类型,但不支持继承和自动合

1. 使用 interface 定义 Person 对象
TypeScript 复制代码
interface PersonInterface {
  name: string
  age: number
  speak(): void
}

// 使用 type 定义 Person 对象
type PersonType = {
  name: string
  age: number
  speak(): void
}

let p1: PersonInterface = {
  name: 'tom'
  age: 18
  speak() {
  console.log(this.name)
  }
}
2. interface 可以集成、合并
TypeScript 复制代码
interface PerssonInterface{
  name: string                 // 姓名
  age: number                  // 年龄
}

interface PersonInterface {
  speak(): void
}

interface StudentInterface extends PersonInterface {
  grade: string                // 年级
}

cosnt student: StudentInterface = {
  name: '李四'
  age: 18
  grade: '高二'
  speak(){
    console.log( this.name, this.age, this.grade)
  }
}
3. type 的交叉类型
TypeScript 复制代码
// 使用 type 定义 Person 类型,并通过交叉类型实现属性的合并
type PersonType = {
  name: string
  age: number
} & {
  speak: () => void
}

// 使用type 定义 Student 类型,并通过交叉类型继承 PersonType
type StudentType = PersonType & {
  grade: string
}
2. interface 与 抽象类的区别

相同点:都用于定义一个类的格式(应该遵循的契约)

不同点:interface:只能描述结构,不能由任何实现代码,一个类可以实现多个接口

抽象类:既可以包含抽象方法,也可以包含具体方法,一个类只能能继承一个抽象类

1.一个类可以实现多个接口
TypeScript 复制代码
// FlyInterface 接口
interface FlyInterface {
  fly(): void
}

// 定义 SwimInterface 接口
interface SwimInterface {
  swim(): void
}

// Duck 类实现了FlyInterface 和 SwimInterface 两个接口
class Duck implements FlyInterface, Swiminterface {
  fly(): void{
    console.log('鸭子可以飞')
  }
  
  swim(): void{
    console.log('鸭子可以游泳')
  }
} 

// 创建一个 Duck 实例
const duck = new Duck()
duck.fly()       // 输出:鸭子可以飞
duck.swim()      // 输出:鸭子可以游泳

五、泛型

泛型允许我们在定义函数、类或接口时,使用类型参数来表示未指定的类型,这些参数在具体使用时,才被指定具体的类型,泛型能让同一段代码适用于多种类型,同时仍然保持类型的安全性。

举例:如下代码中<T>就是 泛型,(不一定非叫 T),设置泛型后即可在函数中使用T表示该类型

1.泛型函数

TypeScript 复制代码
function logData<T>(data:T):T{
  console.log(data)
  return data
}

logData<number>(100)
logData<string>('hello')

2.泛型可以有多个

TypeScript 复制代码
function logData<T, U>(data1:T, data2:U): T | U {
  console.log(data1, data2)
  return Data.now() % 2 ? data1: data2
}

logData<number, string>(100, 'hello')
logData<string, boolean>('ok', false)

3. 泛型接口

TypeScript 复制代码
interface PeronInterface<T>{
  name: string
  age: number
  extraInfo: T
}

let p1: PersonInterface<string>
let p2: PersonInterface<number>

p1 = { name: '张三', age: 18, extraInfo: '好学生'}
p2 = { name: '李四', age: 18, extraInfo: '坏学生'}

4. 泛型约束

TypeScript 复制代码
interface PersonInterface{
  name: string
  age: number
}

function logPerson<T extends PersonInterface>(info: T): void{
  console.log(`姓名:${info.name, 年龄:${info.age}`)
}

logPerson({name: '张三', age: 18})

5. 泛型类

TypeScript 复制代码
class Person <T> {
  constructor(
    public name: string
    public age: number
    public extraInfo: T
  ) {}
  speak() {
    console.log(`姓名:${this.name}, 年龄:${this.age}`)
    console.log(this.extraInfo)
  }
}


const p1 = new Person <string>('小明', 18, '好学生')

type CarInfo = {
  brand: string
  color: string
}

const p2 = new Person<CarInfo>('小李', 25, { brand: '宝马', color: '白色'})

六、类型声明文件

类型声明文件是 TypeScript 中的一种特殊文件,通常以.d.ts 作为扩展名。它的主要作用是为现有的 JavaScript 代码提供类型信息,使得 TypeScript 能够在使用这些 JavaScript 库或模块时进行类型检查和提示
demo.js

TypeScript 复制代码
export function add(a, b){
  return a + b
}

export function mul(a, b){
  return a * b
}

demo.d.ts

TypeScript 复制代码
decleare function add(a: number, b: number): number
decleare function mul(a: number, b: number): number

export{add, mul}

index.ts

TypeScript 复制代码
// example.ts
import {add, mul} from './demo.js'

const x = add(2, 3)   // x 类型为 number
const y = mul(4, 5)   // y 类型为 number

console.log(x, y)

七、装饰器

1. 简介

  1. 装饰器本质是一种特殊的函数,它可以对:类、属性、方法、参数进行扩展,同时能让代码更简洁。
  2. 装饰器自 2015 年在ECMAScript-6 中被提出到现在,以将近10年
  3. 截至目前,装饰器依然是实验性特性,需要开发者手动调整配置,来开启装饰器支持。
  4. 装饰器有 5 种:类、属性、方法、访问器、参数

备注:虽然TypeScript5.0中可以直接使用类装饰器,但为了确保其他装饰器可用,现阶段使用时,仍建议使用 experimentalDecorators 配置来开启装饰器支持,而且不排除在来的版本中,官方会进一步调整装饰器的相关语法

2. 类装饰器

(1) 基本语法

类装饰器是一个应用在类声明上的函数,可以为类添加额外的功能,或添加额外的逻辑。

应用场景:日志记录、元数据添加、类功能增强

TypeScript 复制代码
// 定义装饰器
function ClassDecorator(constructor: Function) {
    console.log('类装饰器执行');
}

// 应用装饰器
@ClassDecorator
class MyClass {
    constructor() {
        console.log('MyClass实例化');
    }
}

const obj = new MyClass();
// 输出:
// 类装饰器执行
// MyClass实例化
TypeScript 复制代码
function LogClass(target: Function) {
    console.log(`类 ${target.name} 被定义`);
}

@LogClass
class User {
    name: string;
    
    constructor(name: string) {
        this.name = name;
    }
}

// 输出:类 User 被定义

3. 装饰器工厂

应用场景:需要传参的装饰器

TypeScript 复制代码
// 装饰器工厂:返回装饰器函数的函数
function Color(color: string) {
    return function(target: Function) {
        console.log(`这个类的颜色是: ${color}`);
    }
}

@Color("红色")
class Car {
    brand: string = "Toyota";
}

// 输出:这个类的颜色是: 红色

4. 装饰器组合

TypeScript 复制代码
function First() {
    console.log("第一个装饰器");
    return function(target: Function) {}
}

function Second() {
    console.log("第二个装饰器");
    return function(target: Function) {}
}

// 从下往上执行
@First()
@Second()
class Example {}

// 输出:
// 第二个装饰器
// 第一个装饰器

5. 属性装饰器

应用场景:属性验证、只读属性、元数据标记

TypeScript 复制代码
function ReadOnly(target: any, propertyName: string) {
    console.log(`属性 ${propertyName} 被装饰`);
    
    // 使属性只读
    Object.defineProperty(target, propertyName, {
        writable: false
    });
}

class Product {
    @ReadOnly
    id: number = 1;
    
    name: string = "手机";
}

const p = new Product();
p.id = 2; // 错误:Cannot assign to 'id' because it is read-only

6. 方法装饰器

应用场景:日志记录、性能监控、方法拦截

TypeScript 复制代码
function LogMethod(target: any, methodName: string, descriptor: PropertyDescriptor) {
    console.log(`方法 ${methodName} 被调用`);
    
    const originalMethod = descriptor.value;
    
    descriptor.value = function(...args: any[]) {
        console.log(`调用参数: ${args}`);
        const result = originalMethod.apply(this, args);
        console.log(`返回结果: ${result}`);
        return result;
    };
}

class Calculator {
    @LogMethod
    add(a: number, b: number): number {
        return a + b;
    }
}

const calc = new Calculator();
calc.add(2, 3);
// 输出:
// 调用参数: 2,3
// 返回结果: 5

7. 访问器装饰器

应用场景:控制getter/setter的行为

TypeScript 复制代码
function Configurable(value: boolean) {
    return function(target: any, propertyName: string, descriptor: PropertyDescriptor) {
        descriptor.configurable = value;
    };
}

class Person {
    private _age: number = 20;
    
    @Configurable(false)  // 设置不可配置
    get age(): number {
        return this._age;
    }
    
    set age(value: number) {
        this._age = value;
    }
}

8. 参数装饰器

应用场景:参数验证、依赖注入标记

TypeScript 复制代码
function LogParameter(target: any, methodName: string, paramIndex: number) {
    console.log(`参数装饰器: 方法 ${methodName} 的第 ${paramIndex} 个参数`);
}

class Greeter {
    greet(@LogParameter name: string, @LogParameter age: number) {
        return `Hello ${name}, age: ${age}`;
    }
}

const g = new Greeter();
g.greet("张三", 25);
// 输出:
// 参数装饰器: 方法 greet 的第 0 个参数
// 参数装饰器: 方法 greet 的第 1 个参数

完整示例:简单的验证装饰器

TypeScript 复制代码
// 方法装饰器:验证参数
function Validate() {
    return function(target: any, methodName: string, descriptor: PropertyDescriptor) {
        const originalMethod = descriptor.value;
        
        descriptor.value = function(...args: any[]) {
            // 验证所有参数都不为空
            for (const arg of args) {
                if (!arg) {
                    throw new Error("参数不能为空");
                }
            }
            return originalMethod.apply(this, args);
        };
    };
}

class UserService {
    @Validate()
    createUser(name: string, email: string) {
        console.log(`创建用户: ${name}, ${email}`);
        return { name, email };
    }
}

const service = new UserService();
service.createUser("张三", "zhangsan@example.com"); // ✓ 成功
service.createUser("", "test@example.com"); // ✗ 报错:参数不能为空

启用装饰器

需要在 tsconfig.json 中配置:

TypeScript 复制代码
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

总结

装饰器类型 参数 应用场景
类装饰器 constructor: Function 类增强、元数据
属性装饰器 target, propertyName 属性验证、只读
方法装饰器 target, methodName, descriptor 日志、性能监控
访问器装饰器 target, propertyName, descriptor 控制getter/setter
参数装饰器 target, methodName, paramIndex 参数验证、依赖注入
相关推荐
web打印社区2 小时前
vue页面打印:printjs实现与进阶方案推荐
前端·javascript·vue.js·electron·html
We་ct2 小时前
LeetCode 30. 串联所有单词的子串:从暴力到高效,滑动窗口优化详解
前端·算法·leetcode·typescript
木卫二号Coding2 小时前
Docker-构建自己的Web-Linux系统-Ubuntu:22.04
linux·前端·docker
CHU7290353 小时前
一番赏盲盒抽卡机小程序:解锁惊喜体验与社交乐趣的多元功能设计
前端·小程序·php
RFCEO3 小时前
前端编程 课程十二、:CSS 基础应用 Flex 布局
前端·css·flex 布局·css3原生自带的布局模块·flexible box·弹性盒布局·垂直居中困难
天若有情6733 小时前
XiangJsonCraft v1.2.0重大更新解读:本地配置优先+全量容错,JSON解耦开发体验再升级
前端·javascript·npm·json·xiangjsoncraft
2501_944525543 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 预算详情页面
android·开发语言·前端·javascript·flutter·ecmascript
打小就很皮...4 小时前
《在 React/Vue 项目中引入 Supademo 实现交互式新手指引》
前端·supademo·新手指引
C澒4 小时前
系统初始化成功率下降排查实践
前端·安全·运维开发