TypeScript在线调试 (opens new window)

# 1.什么是TypeScript

Typescript 是一个强类型的 JavaScript 超集,支持ES6语法,支持面向对象编程的概念,如类、接口、继承、泛型等。
Typescript并不直接在浏览器上运行,需要编译器编译成纯Javascript来运行。

# 2.数据类型​

# 一、基础类型(Primitive Types)

# 1. 原始类型

类型 描述 示例
string 文本类型 let name: string = "Alice"
number 数字(含整数/浮点数) let age: number = 25
boolean 布尔值 let isDone: boolean = true
null 空值 let n: null = null
undefined 未定义 let u: undefined = undefined
symbol 唯一标识符(ES6) const sym: symbol = Symbol('key')
bigint 大整数(ES2020) let big: bigint = 100n

# 2. 特殊原始类型​

类型 描述 示例
void 表示无返回值(函数) function log(): void { console.log('Done') }
never 表示永不返回(如抛出错误) function error(): never { throw new Error() }

# 二、对象类型(Object Types)​

# 1. 数组​

语法 示例
类型[] let nums: number[] = [1, 2]
Array<类型> let strs: Array<string> = ['a', 'b']
只读数组 const roArr: readonly number[] = [1, 2]

# ​2. 元组(Tuple)​

固定长度和类型的数组:

let tuple: [string, number] = ["Alice", 25];
tuple[0] = "Bob"; // OK
tuple[1] = "30";  // 报错:第二个元素必须是 number
1
2
3

# 3. 对象​

// 接口定义
interface User {
  name: string;
  age?: number; // 可选属性
  readonly id: number; // 只读属性
}

// 使用
const user: User = { name: "Alice", id: 1 };
user.name = "Bob"; // OK
user.id = 2;       // 报错:id 是只读的
1
2
3
4
5
6
7
8
9
10
11

# 4. 函数​

// 函数类型
type AddFunc = (a: number, b: number) => number;
// 实现
const add: AddFunc = (x, y) => x + y;
1
2
3
4

# 三、高级类型(Advanced Types)​

# 1. 联合类型(Union)​

let value: string | number;
value = "hello"; // OK
value = 42;      // OK
value = true;    // 报错
1
2
3
4

# 2. 交叉类型(Intersection)​

interface A { a: number }
interface B { b: string }
type C = A & B; // { a: number, b: string }

const obj: C = { a: 1, b: "text" };
1
2
3
4
5

# 3. 字面量类型(Literal)​

let direction: "up" | "down" | "left" | "right";
direction = "up";   // OK
direction = "north"; // 报错
1
2
3

# 4. 枚举(Enum)​

enum Color {
  Red = "RED",
  Green = "GREEN",
}
let color: Color = Color.Red;
1
2
3
4
5

# 5. 类型别名(Type Alias)​

type Point = {
  x: number;
  y: number;
};

const p: Point = { x: 10, y: 20 };
1
2
3
4
5
6

# 6. 索引签名(Index Signature)​

interface StringDict {
  [key: string]: string; // 任意 string 键,值必须是 string
}

const dict: StringDict = {
  name: "Alice",
  age: "25" // 报错:值必须是 string
};
1
2
3
4
5
6
7
8

# 四、其他类型​

# 1. any 与 unknown

类型 描述
any 关闭类型检查(慎用)
unknown 安全版 any,操作前需类型检查
let unsafe: any = "hello";
unsafe.toFixed(); // 编译通过,运行时可能报错
let safe: unknown = "hello";
if (typeof safe === "string") {
  safe.toUpperCase(); // 安全操作
}
1
2
3
4
5
6

# 2. 泛型(Generics)​

function identity<T>(arg: T): T {
  return arg;
}
const output = identity<string>("text"); // 显式指定类型
const inferred = identity(42);           // 自动推断为 number
1
2
3
4
5

# 3.类型声明和类型推断的区别

类型声明是显式地为变量或函数指定类型,而类型推断是TypeScript根据赋值语句右侧的值自动推断变量的类型。例如:

// 类型声明
let x: number;
x = 10;
// 类型推断
let y = 20; // TypeScript会自动推断y的类型为number
1
2
3
4
5

# 4.接口的作用以及使用场景,和类型别名的区别

# 一、接口(Interface)的作用​

接口主要用于​定义对象的形状(Shape)​​,即描述对象应该有哪些属性和方法。它的核心作用是: ​

  • ​约定数据结构​​:强制对象必须符合某种结构。
  • 实现继承(extends)​​:通过继承扩展其他接口。
  • ​​类实现(implements)​​:类可以通过 implements 关键字实现接口的约束。
  1. ​​定义对象类型
interface User {
  name: string;
  age: number;
}
const user: User = { name: "Alice", age: 25 }; // 必须符合接口结构
1
2
3
4
5
  1. ​​函数类型
interface SearchFunc {
  (source: string, keyword: string): boolean;
}
const search: SearchFunc = (src, kw) => src.includes(kw);
1
2
3
4
  1. ​​可索引类型​​(如数组、字典)
interface StringArray {
  [index: number]: string;
}
const arr: StringArray = ["a", "b"];
1
2
3
4
  1. 类的实现约束
interface Animal {
  sound(): void;
}
class Dog implements Animal {
  sound() { console.log("Woof!"); }
}
1
2
3
4
5
6

# 二、类型别名(Type Alias)的作用​

类型别名通过 type 关键字为任意类型(包括原始类型、联合类型、交叉类型等)命名,作用类似于“快捷方式”。

  1. ​​联合类型(Union)
type ID = string | number;
const id1: ID = "123";
const id2: ID = 123;
1
2
3
  1. ​​交叉类型(Intersection)
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;
const person: Person = { name: "Alice", age: 25 };
1
2
3
4
  1. 复杂类型映射
type PartialUser = Partial<User>; // 所有属性变为可选
1
  1. 元组类型
type Point = [number, number];
const p: Point = [10, 20];
1
2
  1. 类型推导与工具类型
type UserKeys = keyof User; // "name" | "age"
1

# 5.泛型(Generics)

# 一. 什么是泛型?​​

泛型类似于 ​​“类型占位符”​​,它允许我们在定义函数、类或接口时 ​​不预先指定具体类型​​,而是在使用时动态传入类型。这样可以:

  • ​提高代码复用性​​:同一套逻辑可以适用于多种类型。
  • ​增强类型安全​​:避免 any 带来的类型丢失问题。
  • ​提供更好的类型推断​​:TS 能自动推断泛型类型。

假设我们要写一个函数,返回传入的参数:

function identity(arg: any): any {
  return arg;
}
1
2
3

使用 any 虽然能接受任何类型,但 ​​丢失了类型信息​​(返回值类型无法确定)。 泛型可以解决这个问题:

function identity<T>(arg: T): T {
  return arg; // 返回值的类型与输入类型一致
}
const result = identity<string>("Hello"); // result 的类型是 string
1
2
3
4

# 二、如何创建泛型函数?​

基本类型参数​

泛型函数在函数名后用 <T> 声明类型参数,T 可以是任意字母(如 K, V 等)。

function echo<T>(value: T): T {
  return value;
}
const str = echo<string>("hello"); // str: string
const num = echo<number>(42);      // num: number

// TS 可以自动推断类型,通常可以省略显式声明:
const inferredStr = echo("hello"); // 自动推断为 string
const inferredNum = echo(42);     // 自动推断为 number
1
2
3
4
5
6
7
8
9

多类型参数​

function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}
const result = pair<string, number>("Alice", 25); // [string, number]
1
2
3
4

泛型约束(Generic Constraints)​

如果需要对泛型类型进行限制,可以用 extends:

interface HasLength {
  length: number;
}
function logLength<T extends HasLength>(arg: T): void {
  console.log(arg.length);
}
logLength("hello"); // OK,string 有 length
logLength([1, 2, 3]); // OK,数组有 length
logLength(42); // 报错:number 没有 length
1
2
3
4
5
6
7
8
9

# 三、泛型的实际用途​

# ​(1) 通用数据结构和算法​

// 数组操作​​(如 map, filter)
function mapArray<T, U>(arr: T[], fn: (item: T) => U): U[] {
  return arr.map(fn);
}
const numbers = [1, 2, 3];
const strings = mapArray(numbers, (n) => n.toString()); // string[]
1
2
3
4
5
6

# ​(2) 动态指定 API 返回的数据类型

interface ApiResponse<T> {
  data: T;
  status: number;
}
function fetchUser(): ApiResponse<{ name: string }> {
  return { data: { name: "Alice" }, status: 200 };
}
1
2
3
4
5
6
7

# ​(3) React 组件 Props​

interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

function List<T>({ items, renderItem }: ListProps<T>) {
  return <div>{items.map(renderItem)}</div>;
}

// 使用
<List<number> 
  items={[1, 2, 3]} 
  renderItem={(n) => <div>{n}</div>} 
/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 6.枚举(Enum)

# 一、枚举是什么?

枚举允许开发者定义一组相关的常量,并为它们赋予有意义的名称。例如:

enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT",
}
1
2
3
4
5
6

# 二、枚举的应用案例

# ​(1) 状态管理​

enum OrderStatus {
  Pending = "PENDING",
  Shipped = "SHIPPED",
  Delivered = "DELIVERED",
  Cancelled = "CANCELLED",
}
function updateOrder(status: OrderStatus) {
  console.log(`Order status: ${status}`);
}
updateOrder(OrderStatus.Shipped); // "Order status: SHIPPED" 
updateOrder("PENDING"); // 报错:必须使用枚举
1
2
3
4
5
6
7
8
9
10
11

# (2) HTTP 状态码​

enum HttpStatus {
  OK = 200,
  BadRequest = 400,
  Unauthorized = 401,
  NotFound = 404,
}
function handleResponse(code: HttpStatus) {
  if (code === HttpStatus.OK) {
    console.log("Success!");
  }
}
1
2
3
4
5
6
7
8
9
10
11

# 三、枚举的类型​

TypeScript 支持 ​​数字枚举​​、​​字符串枚举​​ 和 ​​异构枚举​​(混合 string 和 number)。

# (1) 数字枚举(默认)​

enum StatusCode {
  Success, // 0(默认从 0 开始)
  NotFound = 404, // 可以手动赋值
  ServerError, // 405(自动递增)
}
console.log(StatusCode.Success); // 0
console.log(StatusCode[404]); // "NotFound"(反向映射)
1
2
3
4
5
6
7

# (2) 字符串枚举

enum LogLevel {
  Error = "ERROR",
  Warn = "WARN",
  Info = "INFO",
}
console.log(LogLevel.Error); // "ERROR"
// LogLevel["ERROR"] 不能反向查找(字符串枚举不支持)
1
2
3
4
5
6
7

# (3) 异构枚举(混合 string 和 number)​

enum MixedEnum {
  Yes = 1,
  No = "NO",
}
console.log(MixedEnum.Yes); // 1
console.log(MixedEnum.No); // "NO"
1
2
3
4
5
6

# 四、常量枚举(const enum)

常量枚举是一种优化手段,它会在编译时 ​​内联枚举值​​,减少运行时开销。

const enum LogLevel {
  Error = "ERROR",
  Warn = "WARN",
}
// 编译后:
console.log("ERROR" /* LogLevel.Error */); // 直接替换为 "ERROR"
1
2
3
4
5
6

# 7.处理可空类型(Nullable Types)

在TypeScript中,可空类型是指一个变量可以存储特定类型的值,也可以存储null或undefined。

为了声明一个可空类型,可以使用联合类型(Union Types)

let numberOrNull: number | null = 10; 
numberOrNull = null; // 可以赋值为null 
    
let stringOrUndefined: string | undefined = "Hello"; 
stringOrUndefined = undefined; // 可以赋值为undefined
1
2
3
4
5

# 8.什么是联合类型和交叉类型

联合类型表示一个值可以是多种类型中的一种
而交叉类型表示一个新类型,它包含了多个类型的特性。

# 联合类型示例:

// typescript
let myVar: string | number;
myVar = "Hello"; // 合法
myVar = 123; // 合法

1
2
3
4
5

# 交叉类型示例:

interface A {
  a(): void;
}
interface B {
  b(): void;
}
type C = A & B; // 表示同时具备 A 和 B 的特性
1
2
3
4
5
6
7

# 9.命名空间(Namespace)和模块(Module)

# 一. 命名空间(Namespace)​

命名空间是 ​​TypeScript 特有的​​ 代码分组机制,用于在全局作用域内划分逻辑单元,避免命名冲突。

  • 特点​​

    • ​作用域隔离​​:内部变量/类/函数默认仅在命名空间内可见。
    • ​全局扩展​​:可通过 declare global 或合并声明扩展全局类型。
    • ​依赖手动加载​​:需通过 <script> 标签或打包工具合并文件。
  • 使用场景​​

    • 旧版代码迁移(兼容全局变量模式)。
    • 为第三方库提供类型声明(如 jQuery 的 $.ajax)。
    • 小型项目快速组织代码(不推荐大型项目)。
// 定义命名空间
namespace MyMath {
  export function add(a: number, b: number): number {
    return a + b;
  }
}
// 使用
console.log(MyMath.add(1, 2)); // 3
1
2
3
4
5
6
7
8

# 多文件命名空间

通过 /// <reference path="..." /> 手动合并文件:

// file1.ts
namespace MyMath {
  export const PI = 3.14;
}
// file2.ts
/// <reference path="file1.ts" />
namespace MyMath {
  export function circleArea(radius: number): number {
    return PI * radius * radius; // 访问其他文件的成员
  }
}
1
2
3
4
5
6
7
8
9
10
11

# 二. 模块(Module)

模块是 ​​ES6 标准​​ 的代码封装方式,每个文件是一个独立模块,通过 import/export 管理依赖。

  • 特点​​
    • 文件即模块​​:每个 .ts 文件默认是模块,顶层作用域变量不污染全局。
    • 显式依赖​​:必须通过 import 引入其他模块。
    • 现代项目标配​​:与 Webpack/Rollup 等打包工具深度集成。
  • ​使用场景​​
    • 大型项目或前端框架(React/Vue)。
    • 需要 Tree Shaking 优化代码体积。
    • 团队协作时代码解耦。
// math.ts(模块文件)
export function add(a: number, b: number): number {
  return a + b;
}
// app.ts(使用模块)
import { add } from './math';
console.log(add(1, 2)); // 3
1
2
3
4
5
6
7

# 模块类型​

ES Modules(推荐)​​

import { func } from './module';
export default class MyClass {}
1
2

​​CommonJS(Node.js)​​:

const fs = require('fs');
module.exports = { myFunc };
1
2

# 10.类型断言(Type Assertion)

类型断言(Type Assertion)是 TypeScript 中一种 ​​显式告诉编译器变量类型​​ 的机制,它允许开发者覆盖 TypeScript 的默认类型推断

类型断言类似于其他语言中的 ​​类型转换​​,但 ​​不进行运行时检查​​,仅影响编译阶段的类型推断。

  • 它有两种语法形式:
    • ​​尖括号语法​​(<Type>value
    • ​as 语法​​(value as Type,推荐在 JSX 中使用)
// 场景:从 API 获取的数据,开发者知道是 `User` 类型,但 TS 推断为 `any`
const userData: any = { name: "Alice", age: 25 };
// 类型断言明确告诉编译器这是 `User` 类型
interface User {
  name: string;
  age: number;
}
// 语法1:尖括号(不适用于 JSX)
const user1 = <User>userData;
// 语法2:as(推荐,兼容 JSX)
const user2 = userData as User;
console.log(user1.name); // "Alice"(类型安全访问)
1
2
3
4
5
6
7
8
9
10
11
12

# 类型断言 vs 类型声明​

特性 ​​类型断言(Type Assertion)​ ​​类型声明(Type Annotation)​​
​​作用时机​ 覆盖编译器的类型推断 声明变量时指定类型
​​运行时影响​ 无(纯编译时行为)
适用场景​ 开发者比编译器更了解类型时 变量初始化时明确类型
// 类型声明(初始化时明确类型)
let age: number = 25;
// 类型断言(覆盖推断)
let unknownData: unknown = "Hello";
let strLength = (unknownData as string).length;
1
2
3
4
5

# 使用场景​

# (1) 处理 any 或 unknown 类型​

const data: unknown = JSON.parse('{"name": "Bob"}');
const userName = (data as { name: string }).name; // 安全访问
1
2

# (2) 访问联合类型的特定成员

function formatInput(input: string | number) {
  // 明确告诉编译器此时是 string
  if ((input as string).trim) {
    return (input as string).trim();
  }
  return input.toString();
}
1
2
3
4
5
6
7

# (3) 强制转换 DOM 元素类型​

const button = document.getElementById("submit") as HTMLButtonElement;
button.disabled = true; // 明确知道是按钮元素
1
2

# ​(4) 非空断言(! 后缀)​

// 告诉编译器变量一定不是 null/undefined
function greet(name?: string) {
  console.log(name!.toUpperCase()); // 谨慎使用!
}
1
2
3
4

# 注意事项​

  1. 避免滥用​

类型断言会绕过 TypeScript 的类型检查,滥用可能导致运行时错误。

// 错误示范​​:
const num = "123" as number; // 编译通过,但运行时是字符串!
console.log(num.toFixed(2)); // 运行时报错
1
2
3

# 11.TypeScript中的可选参数和默认参数

  • 可选参数允许函数中的某些参数不传值,在参数后面加上问号?表示可选。
function greet(name: string, greeting?: string) {
  if (greeting) {
    return `${greeting}, ${name}!`;
  } else {
    return `Hello, ${name}!`;
  }
}
1
2
3
4
5
6
7
  • 默认参数允许在声明函数时为参数指定默认值,这样如果调用时未提供参数值,则会使用默认值。
function greet(name: string, greeting: string = "Hello") {
  return `${greeting}, ${name}!`;
}
1
2
3

# 12.索引类型(Index Types)

索引类型(Index Types)是 TypeScript 中一种 ​​动态访问和操作对象属性类型​​ 的高级特性,它通过 ​​键名映射​​ 和 ​​类型运算​​ 实现灵活的类型安全操作。

索引类型允许开发者通过 ​​字符串或数字字面量类型​​ 动态访问对象属性的类型,核心依赖两个操作符:

  • keyof T​​:获取类型 T 的所有​​可访问键名​​的联合类型。
  • ​T[K]​​:通过键名 K 获取类型 T 中对应属性的类型。
interface User {
  name: string;
  age: number;
  address: string;
}
// 获取 User 所有键名的联合类型
type UserKeys = keyof User; // "name" | "age" | "address"
// 动态获取属性类型
type NameType = User["name"]; // string
type AgeType = User["age"];   // number
1
2
3
4
5
6
7
8
9
10

# 核心优势​

# ​(1) 类型安全的动态属性访问​

避免硬编码属性名,通过泛型约束确保代码类型安全。

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]; // 返回值类型动态推断为 T[K]
}

const user: User = { name: "Alice", age: 25, address: "123 Main St" };
const name = getProperty(user, "name"); // string(类型正确)
const age = getProperty(user, "age");   // number(类型正确)
const invalid = getProperty(user, "email"); // 报错:"email" 不是 User 的键
1
2
3
4
5
6
7
8

# (2) 映射类型(Mapped Types)的基础​

索引类型是生成映射类型(如 Partial<T>、Readonly<T>)的核心。

// 内置的 Partial<T> 实现原理
type Partial<T> = {
  [P in keyof T]?: T[P]; // 将所有属性变为可选
};

type PartialUser = Partial<User>;
// 等价于 { name?: string; age?: number; address?: string; }
1
2
3
4
5
6
7

# (3) 约束对象键名的合法性​

在泛型中限制函数参数必须为对象的有效键名。

function setProperty<T, K extends keyof T>(
  obj: T,
  key: K,
  value: T[K]
): void {
  obj[key] = value; // 键和值类型严格匹配
}
setProperty(user, "age", 30);      // 正确
setProperty(user, "age", "30");    // 报错:类型不匹配
setProperty(user, "email", "test"); // 报错:无效键名
1
2
3
4
5
6
7
8
9
10

# 实际应用场景​

# (1) 类型安全的字典对象​

type Dictionary<T> = {
  [key: string]: T; // 任意字符串键,值类型为 T
};

const users: Dictionary<User> = {
  alice: { name: "Alice", age: 25 },
  bob: { name: "Bob", age: 30 }
};
1
2
3
4
5
6
7
8

# 13.const 和 readonly 对比

constreadonly 都是用于增强代码不可变性的机制,但它们在作用范围、适用场景和约束级别上有显著区别

特性 const readonly
作用目标​ 变量绑定(Variable Binding) 属性(Property)或索引签名
​​约束级别​​ 禁止重新赋值 禁止属性修改
​​适用场景​ 基础类型、对象引用 对象属性、数组、类成员、接口
可变性绕过​ 不能绕过(严格约束) 可通过类型断言/拷贝绕过
​运行时保护​ 无(纯编译时) 无(纯编译时),需配合 Object.freeze
典型用途​ 声明常量 定义不可变的对象结构

# 具体场景对比​

# (1) 基础类型​

// const:变量不可重新赋值
const PI = 3.14;
PI = 3.14159; // 报错:Cannot assign to 'PI'
// readonly:不适用于基础类型(仅用于属性)
let readonlyNum: readonly number = 1; // 无意义,通常不这样用
1
2
3
4
5

# ​(2) 对象

// const:对象引用不可变,但属性可修改
const user = { name: "Alice" };
user.name = "Bob"; // 允许修改属性
user = { name: "Charlie" }; // 报错:不能重新赋值
// readonly:属性不可修改
interface ReadonlyUser {
  readonly name: string;
}
const user: ReadonlyUser = { name: "Alice" };
user.name = "Bob"; // 报错:Cannot assign to 'name'
1
2
3
4
5
6
7
8
9
10

# ​(3) 数组​

// const:数组引用不可变,元素可修改
const arr = [1, 2, 3];
arr[0] = 10; // 允许修改元素
arr = [4, 5, 6]; // 报错:不能重新赋值

// readonly:元素不可修改
const readonlyArr: readonly number[] = [1, 2, 3];
readonlyArr[0] = 10; // 报错:Index signature in type 'readonly number[]' only permits reading
1
2
3
4
5
6
7
8

# ​(4) 类与接口​

class Person {
  readonly id: number; // 实例化后不可修改
  constructor(id: number) {
    this.id = id;
  }
}
const person = new Person(1);
person.id = 2; // 报错:Cannot assign to 'id'
1
2
3
4
5
6
7
8

# 如何选择?​​

  • ​​优先用 const 的场景​​
    • 声明基础类型的常量(如 const MAX_SIZE = 100)。
    • 确保对象/数组的引用不变(防止重新赋值)。
  • 优先用 readonly 的场景​​
    • 定义接口或类的不可变属性(如 readonly id: string)。
    • 限制函数参数不被修改(如 function log(config: Readonly<Config>))。
    • 配合 Readonly<T> 工具类型快速生成不可变对象。

# 总结​​

  • ​const​​:保护变量引用不被重新赋值,适用于所有数据类型。
  • ​​readonly​​:保护对象属性不被修改,适用于接口、类和数组。
  • ​联合使用​​:const + Readonly<T> 可实现完全不可变结构。

# 14.any 类型的作用与滥用后果

# 一、any 的核心作用​

# ​(1) 动态类型兼容​

允许变量接受​​任意类型​​的值,相当于关闭类型检查:

let dynamicData: any = "Hello";
dynamicData = 42;      // 允许从 string 切换到 number
dynamicData = { id: 1 }; // 允许赋值为对象
1
2
3

# ​(2) 渐进式迁移 JavaScript 代码​

在将 JS 项目迁移到 TS 时,any 可作为临时过渡手段

// 旧 JS 代码迁移示例
function legacyParse(data) {  // 隐式 any
  return JSON.parse(data);
}
// 初步迁移为 TS(暂时用 any)
function tsParse(data: any): any {
  return JSON.parse(data);
}
1
2
3
4
5
6
7
8

# (3) 与无类型库交互​

处理第三方 JS 库或动态 API 响应时

const thirdPartyLib: any = require('untyped-lib');
const result = thirdPartyLib.method(); // 不触发类型报错
1
2

# 二、滥用 any 的后果​

# ​(1) 类型安全彻底失效​

function calculateTotal(price: any, quantity: any): any {
  return price * quantity; // 如果传入字符串,返回 NaN(无编译时报错)
}
1
2
3
  • 后果​​:运行时错误(如 undefined is not a function)无法在编译时捕获。

# (2) 阻碍类型系统优化​

let unsafe: any = "Hello";
const len = unsafe.length; // 不报错,但 unsafe 可能是 number
const arr = unsafe.split(","); // 编译通过,运行时可能崩溃
1
2
3

# 三. 替代方案(避免滥用 any)​

# (1) 使用更精确的顶层类型​

场景 替代方案 优点
未知对象结构 unknown 必须类型检查后才能操作
部分属性可选 Partial<T> 明确哪些属性可能缺失
动态键值对象 Record<string, T> 约束值的类型
// 用 unknown 替代 any(安全操作需类型收窄)
function safeParse(data: string): unknown {
  return JSON.parse(data);
}
const result = safeParse('{"id":1}');
if (result && typeof result === 'object' && 'id' in result) {
  console.log(result.id); // 类型安全访问
}
1
2
3
4
5
6
7
8

# (2) 类型断言(as)替代部分 any​

当开发者比编译器更了解类型时:

const element = document.getElementById("root") as HTMLElement; // 非 any
1

# ​(3) 泛型保持灵活性​

function identity<T>(arg: T): T {
  return arg; // 不丢失类型信息
}
1
2
3

# ​(4) 逐步替换遗留 any​

// @ts-expect-error - TODO: 迁移后替换为具体类型
const legacyData: any = fetchData();
1
2

# 四、何时可以合理使用 any?​

  • (1) 临时快速原型开发​​
    • 短期验证逻辑时使用,完成后立即替换为具体类型。
  • ​(2) 类型体操过于复杂时​​
    • 当类型系统无法简洁表达复杂逻辑时(如递归类型深度过大)。
  • (3) 类型声明文件(.d.ts)中​​
    • 为无类型 JS 库编写声明时,暂时用 any 占位:
declare module "untyped-module" {
  export const config: any; // 后续逐步补充具体类型
}
1
2
3

# 15.TS 中 this 的注意事项

# 一、默认情况下 this 的类型问题​

在非严格模式下,未指定类型的 this 会被推断为 any,失去类型安全:

// 隐式 any
function logThis() {
  console.log(this.name); // this 是 any,无类型检查
}
1
2
3
4

# 解决方案:启用 --noImplicitThis

在 tsconfig.json 中启用该选项,强制显式声明 this 类型:

{
  "compilerOptions": {
    "noImplicitThis": true
  }
}
1
2
3
4
5

# 二、函数中的 this 类型​

# (1) 普通函数​​

需手动指定 this 类型(通过第一个参数):

interface User {
  name: string;
}
function greet(this: User) {
  console.log(`Hello, ${this.name}`);
}
const user = { name: "Alice" };
greet.call(user); // 正确
greet(); // 报错:this 未绑定
1
2
3
4
5
6
7
8
9

# (2) 箭头函数​​

箭头函数继承外层 this,​​无法通过参数声明类型​​,需通过变量类型推断:

class Timer {
  seconds = 0;
  start() {
    setInterval(() => {
      console.log(this.seconds++); // this 正确指向 Timer 实例
    }, 1000);
  }
}
1
2
3
4
5
6
7
8

# 三、类中的 this

# ​(1) 类方法中的 this

默认指向当前类实例,类型自动推断

class Counter {
  count = 0;
  increment() {
    this.count++; // this: Counter
  }
}
1
2
3
4
5
6

# (2) 回调函数中的 this 丢失​

将类方法作为回调传递时,this 可能丢失

class Button {
  text = "Click me";
  handleClick() {
    console.log(this.text); // 运行时可能报错
  }
}
const button = new Button();
document.addEventListener("click", button.handleClick); // this 指向 DOM 元素
1
2
3
4
5
6
7
8

修复方法​

  1. ​​使用箭头函数​​
class Button {
  handleClick = () => {
    console.log(this.text); // this 永远指向实例
  };
}
1
2
3
4
5
  1. ​​显式绑定 this​​
document.addEventListener("click", button.handleClick.bind(button));
1

# 16.协变、逆变、双变和抗变

​​协变(Covariance)​​、​​逆变(Contravariance)​​、​​双变(Bivariance)​​ 和 ​​抗变(Invariance)​​ 描述了复杂类型(如函数、泛型)的子类型关系如何传递

# 一. 基本概念​

# ​(1) 子类型(Subtyping)​​

若类型 A 是类型 B 的子类型(记作 A <: B),则 A 的值可以安全赋值给 B 的变量。 例如:Cat <: Animal(猫是动物的子类型)。

# ​​(2) 变型(Variance)​​

描述​​容器类型​​(如 Array<T>、函数参数)的子类型关系如何随内部类型 T 变化。

# 二. 四种变型

# (1) 协变(Covariance)​​

  • ​​定义​​:如果 A <: B,则 Container<A><: Container<B>
  • ​​特点​​:子类型关系与内部类型​​方向一致​​。
  • ​示例​​:TypeScript 的​​数组​​和​​返回值类型​​是协变的:
interface Animal { name: string }
interface Cat extends Animal { meow(): void }

let animals: Animal[] = [];
let cats: Cat[] = [{ name: "Whiskers", meow: () => {} }];
animals = cats; // 协变:Cat[] <: Animal[]
1
2
3
4
5
6

# (2) 逆变(Contravariance)​

  • ​定义​​:如果 A <: B,则 Container<B> <: Container<A>
  • ​特点​​:子类型关系与内部类型​​方向相反​​。
  • ​示例​​:TypeScript 的​​函数参数类型​​是逆变的(默认情况下):
type AnimalHandler = (animal: Animal) => void;
type CatHandler = (cat: Cat) => void;

let handleAnimal: AnimalHandler = (a: Animal) => {};
let handleCat: CatHandler = (c: Cat) => { c.meow() };

// 逆变:AnimalHandler <: CatHandler
handleCat = handleAnimal; // 允许
handleAnimal = handleCat; // 报错(除非启用 strictFunctionTypes: false)
1
2
3
4
5
6
7
8
9

# (3) 双变(Bivariance)​

  • ​定义​​:Container<A> 和 Container<B>互相兼容,无论 A 和 B 的关系。
  • ​特点​​:​​最宽松但最不安全​​,TypeScript 中函数参数的默认行为(非严格模式)。
  • ​​示例​​:
// 非严格模式下(tsconfig.json 中 strictFunctionTypes: false)
handleAnimal = handleCat; // 允许
handleCat = handleAnimal; // 允许
1
2
3

# (4) 抗变(Invariance)​

  • 定义​​:Container<A> 和 Container<B> 仅当 A === B 时才兼容。
  • ​特点​​:​​最严格​​,TypeScript 中泛型默认行为(如 interface Box<T>{ value: T })。
  • ​示例​​:
interface Box<T> { value: T }
let animalBox: Box<Animal> = { value: { name: "Milo" } };
let catBox: Box<Cat> = { value: { name: "Whiskers", meow: () => {} } };

animalBox = catBox; // 报错(抗变)
catBox = animalBox; // 报错
1
2
3
4
5
6
lastUpdate: 5/29/2025, 3:55:49 PM