
目录
-
Q1. TypeScript 和 JavaScript 有什么区别?为什么需要 TypeScript?
-
Q2. 什么是类型注解和类型推断?
-
Q3. interface 和 type 有什么区别?各自的使用场景是什么?
-
Q4. 什么是联合类型和交叉类型?
-
Q5. unknown 和 any 有什么区别?为什么更推荐使用 unknown?
-
Q6. 什么是枚举?枚举有哪些类型?
-
Q7. 什么是泛型?举例说明泛型的基本用法
-
Q8. type 和 interface 都能定义函数类型,两种写法有什么区别?
-
Q9. TypeScript 中的 null 和 undefined 有什么区别?
-
Q10. 如何声明一个只读数组?对比 ReadonlyArray 和 as const
Q6. 什么是枚举?枚举有哪些类型?
考点
TypeScript 枚举的语法、数字枚举、字符串枚举、常量枚举,以及各自的编译结果和性能影响
答案
枚举(Enum)用于给一组相关数值起一个语义化的名字:
// 数字枚举(最常用)
enum Status {
Pending, // 0
Success, // 1
Error // 2
}
// 字符串枚举
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
// 常量枚举(编译后完全消除,无运行时开销)
const enum Direction2 {
Up = "UP",
Down = "DOWN",
}
TypeScript 中的枚举分为三种:数字枚举、字符串枚举、常量枚举。
知识点详解
数字枚举的自动递增
enum Status {
Pending = 1, // 从 1 开始
Success, // 2
Error, // 3
Unknown // 4
}
枚举的反向映射
数字枚举的值可以反向获取键名:
enum Status {
Pending = 1,
Success = 2,
}
// "Pending"(反向映射,不推荐在生产代码中使用)
const statusName = Status[1];
常量枚举的性能优势
// 普通枚举
enum Status {
Pending,
Success,
}
// 编译后:var s = Status.Pending;(保留运行时引用)
const s = Status.Pending;
// 常量枚举:编译后完全消除
const enum Status2 {
Pending,
Success,
}
// 编译后:const s2 = 0;(内联到代码中)
const s2 = Status2.Pending;
实际项目中的使用场景
数字枚举:后端接口返回的状态码
enum OrderStatus {
Pending = 1, // 待支付
Paid = 2, // 已支付
Shipped = 3, // 已发货
Delivered = 4, // 已送达
Cancelled = 5, // 已取消
}
function getOrderStatusText(status: OrderStatus): string {
switch (status) {
case OrderStatus.Pending: return "待支付";
case OrderStatus.Paid: return "已支付";
case OrderStatus.Shipped: return "已发货";
case OrderStatus.Delivered: return "已送达";
case OrderStatus.Cancelled: return "已取消";
default: return "未知状态";
}
}
字符串枚举:明确的业务含义
enum Environment {
Development = "dev",
Staging = "staging",
Production = "prod",
}
function getApiBaseUrl(env: Environment): string {
const urls: Record<Environment, string> = {
[Environment.Development]: "http://localhost:3000",
[Environment.Staging]: "https://staging-api.example.com",
[Environment.Production]: "https://api.example.com",
};
return urls[env];
}
常量枚举:性能敏感场景
// 常量枚举在编译时完全内联,没有任何运行时开销
const enum CacheKey {
User = "user",
Token = "token",
Config = "config",
}
// 编译后 localStorage.setItem("user", ...)
localStorage.setItem(CacheKey.User, userData);
Q7. 什么是泛型?举例说明泛型的基本用法
考点
泛型的核心概念、泛型约束、泛型函数、泛型接口、泛型类的使用
答案
泛型(Generics)是一种参数化类型的机制,让同一段代码能支持多种数据类型,同时保留类型安全。
简单理解:泛型就是类型的"变量"。
// 不用泛型:每个类型都要写一遍
function identityString(arg: string): string { return arg; }
function identityNumber(arg: number): number { return arg; }
// 用泛型:一个函数搞定所有类型
function identity<T>(arg: T): T {
return arg;
}
identity<string>("hello"); // 显式指定 T 为 string
identity(42); // TS 自动推断 T 为 number
identity(true); // TS 自动推断 T 为 boolean
知识点详解
泛型函数
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
// number
const nums = firstElement([1, 2, 3]);
// string
const strs = firstElement(["a", "b", "c"]);
泛型接口
interface ApiResponse<T> {
code: number;
data: T;
message: string;
}
// 使用时指定具体类型
type UserResponse = ApiResponse<User>;
type ListResponse = ApiResponse<User[]>;
泛型约束(extends)
限制泛型的范围,确保类型具有特定的属性:
// T 必须有 length 属性
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length > b.length ? a : b;
}
longest("hello", "world"); // OK,都有 length
longest([1, 2], [3, 4, 5]); // OK,数组有 length
longest(1, 2); // ❌ number 没有 length
// keyof 约束:确保 key 是对象的键
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "张三", age: 25 };
getProperty(user, "name"); // 返回 string
getProperty(user, "age"); // 返回 number
getProperty(user, "email"); // ❌ "email" 不在 user 中
泛型类
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
}
const numStack = new Stack<number>();
numStack.push(1);
numStack.push(2);
numStack.pop(); // number | undefined
实际项目中的使用场景
1. 统一的 API 请求封装
interface ApiResponse<T> {
code: number;
data: T;
message: string;
}
async function request<T>(url: string): Promise<ApiResponse<T>> {
const res = await fetch(url);
return res.json();
}
// 调用时指定返回类型
const userRes = await request<User>("/api/user/1");
console.log(userRes.data.name);
2. 通用表单字段类型
interface FormField<T> {
value: T;
onChange: (value: T) => void;
label: string;
validate?: (value: T) => string | null;
}
const nameField: FormField<string> = {
value: "",
onChange: (v) => console.log(v),
label: "姓名",
};
const ageField: FormField<number> = {
value: 0,
onChange: (v) => console.log(v),
label: "年龄",
};
Q8. type 和 interface 都能定义函数类型,两种写法有什么区别?
考点
函数类型的两种定义方式对比:类型别名 vs 接口,以及各自的扩展性和使用场景
答案
两种写法都能定义函数类型:
// 方式 1:type 定义函数类型
type Handler = (event: MouseEvent) => void;
// 方式 2:interface 定义函数类型(call signature)
interface Handler {
(event: MouseEvent): void;
}
// 使用上没有区别
const onClick: Handler = (e) => console.log(e.clientX);
区别:
-
type可以用交叉类型扩展,interface用extends扩展 -
interface支持声明合并,同名会自动合并 -
type可以定义联合类型、交叉类型、元组等复杂类型 -
interface的意图更明确(定义对象/类结构),type更灵活(定义任意类型别名)
使用建议: 如果只是定义函数类型,两种都可以;如果要和其他类型组合或做复杂运算,用 type。
知识点详解
call signature(函数签名)语法
interface Compare {
// 箭头表示返回值
(a: number, b: number): number;
}
// 等价于 type
type Compare2 = (a: number, b: number) => number;
// 使用
const ascending: Compare = (a, b) => a - b;
混合接口:对象 + 函数
interface 可以同时描述属性和函数,这是 type 做不到的:
interface Counter {
count: number;
(): void; // 可作为函数调用
reset(): void; // 也有方法
}
function createCounter(): Counter {
const c = (() => { c.count++; }) as Counter;
c.count = 0;
c.reset = () => { c.count = 0; };
return c;
}
泛型函数类型
// type 更灵活
type Serializer<T> = (value: T) => string;
// interface 需要用泛型参数
interface Serializer2<T> {
(value: T): string;
}
实际项目中的使用场景
type 的典型场景:
// 事件处理器类型
type EventHandler<T = unknown> = (event: T) => void;
// 多种回调的统一类型
type AsyncCallback<T> = (error: Error | null, result: T | null) => void;
// 联合函数类型(函数重载)
type Factory = ((a: string) => string) | ((a: number) => number);
interface 的典型场景:
// React 组件的 Props 类型天然用 interface
interface ButtonProps {
onClick: (e: React.MouseEvent) => void;
children: React.ReactNode;
}
// 定义一个可调用对象(类组件旧写法)
interface CallSignature {
(x: number): number;
name: string;
}
Q9. TypeScript 中的 null 和 undefined 有什么区别?
考点
null 与 undefined 的语义区别、TS 中的配置(strictNullChecks)、最佳实践
答案
| 对比维度 | null | undefined |
|---|---|---|
| 语义 | "这里应该有一个值,但它是空的" | "这里还没有被赋值" |
| 典型场景 | 显式声明"没有值" | 变量声明但未赋值、函数无返回值 |
| typeof | "object" | "undefined" |
| == 相等 | null == undefined 为 true | undefined == null 为 true |
| === 严格相等 | false | false |
let a: string | null = null; // 显式表示"没有值"
let b: string | undefined; // 未赋值,默认是 undefined
// 严格模式下,两者不会自动互相转换
"str" + null; // "strnull"
"str" + undefined; // "strundefined"
知识点详解
strictNullChecks 的影响
开启 strictNullChecks 后(现代 TS 项目默认开启),所有可能为 null/undefined 的值都必须显式处理:
// strictNullChecks: false(不推荐)
function greet(name: string) {
// 如果 name 是 null,运行时崩溃
return name.toUpperCase();
}
// strictNullChecks: true(推荐)
function greetSafe(name: string | null) {
if (name === null) return "匿名用户";
// TS 知道这里 name 不是 null
return name.toUpperCase();
}
安全的空值判断
// ❌ 不安全的写法
const len = str.length; // str 可能是 null/undefined,运行时报错
// ✅ 安全写法 1:非空判断
if (str !== null && str !== undefined) {
console.log(str.length);
}
// ✅ 安全写法 2:可选链 + 空值合并
console.log(str?.length ?? 0);
// ✅ 安全写法 3:空值合并(??)替代 ||
// || 会把 0、""、false 也当作假值
const len2 = str?.length ?? 0;
const name = inputName || "默认"; // ❌ "" 也被替换
const name2 = inputName ?? "默认"; // ✅ 只有 null/undefined 被替换
实际项目中的使用场景
明确区分使用场景:
// null:这里"没有值"是业务含义,如用户未设置头像
interface User {
name: string;
avatar: string | null; // 有 avatar 字段,但当前为空
}
// undefined:这里"还没有设置",如对象属性不存在
interface Config {
debug?: boolean; // 可选属性,不写就是 undefined
}
const config: Config = {};
config.debug; // undefined,表示未设置
清除引用以便垃圾回收:
// 将对象引用设为 null,让垃圾回收器可以回收内存
class Cache {
private data: HeavyObject | null = null;
set(obj: HeavyObject) {
this.data = obj;
}
clear() {
this.data = null; // 释放引用
}
}
判断变量为 null 或 undefined(推荐写法):
// 判断严格等于 null(常用)
if (value === null) { ... }
// 判断为 null 或 undefined(推荐)
if (value == null) { ... }
// 等价于 value === null || value === undefined
// 比 value === null || value === undefined 更简洁
Q10. 如何声明一个只读数组?对比 ReadonlyArray 和 as const
考点
TypeScript 中的只读声明方式:ReadonlyArray、readonly []、as const 的区别和使用场景
答案
TypeScript 提供了三种声明只读数组的方式:
// 方式 1:ReadonlyArray(类型层面声明)
let nums: ReadonlyArray<number> = [1, 2, 3];
nums.push(4); // ❌ 编译报错
nums[0] = 10; // ❌ 编译报错
// 方式 2:readonly T[](更简洁的语法糖)
let nums2: readonly number[] = [1, 2, 3];
nums2.push(4); // ❌ 报错
// 方式 3:as const(字面量常量断言,深度只读)
const nums3 = [1, 2, 3] as const;
nums3.push(4); // ❌ 报错
nums3[0] = 10; // ❌ 报错
核心区别:
| 对比维度 | ReadonlyArray | as const |
|---|---|---|
| 语法 | 类型注解写法 | 字面量断言写法 |
| 数组元素类型 | 可指定为泛型参数 | 自动推断为最窄类型(字面量) |
| 用途 | 函数参数、变量类型声明 | 声明常量数组 |
| 对象属性只读 | ❌ 不支持 | ✅ 支持 |
| 元组支持 | ✅ 支持 | ✅ 支持 |
知识点详解
as const 的类型收窄效果
// 普通数组:元素类型被推断为 number(宽泛)
const directions = ["north", "south", "east", "west"];
directions[0] = "center"; // ❌ 但仍可修改元素
directions.push("center"); // ❌ 仍可添加元素
// as const:元素被推断为具体的字面量类型
const directions2 = ["north", "south", "east", "west"] as const;
// "north" | "south" | "east" | "west"
type Direction = typeof directions2[number];
directions2[0] = "center"; // ❌
实际项目中的使用场景
ReadonlyArray:函数参数,防止函数内部修改传入的数组
function sum(nums: readonly number[]): number {
// nums.push(1); // ❌ 防止意外修改
return nums.reduce((a, b) => a + b, 0);
}
const arr = [1, 2, 3];
sum(arr); // OK
sum([1, 2, 3]); // OK
as const:配置常量、路由表、选项列表
// 路由配置
const ROUTES = {
home: "/",
about: "/about",
user: "/user",
settings: "/settings",
} as const;
type Route = typeof ROUTES[keyof typeof ROUTES]; // "/" | "/about" | ...
// 菜单配置
const MENU_ITEMS = [
{ label: "首页", path: "/" },
{ label: "关于", path: "/about" },
] as const;
// 状态常量
const STATUS = {
PENDING: "pending",
SUCCESS: "success",
ERROR: "error",
} as const;
function getStatusText(s: typeof STATUS[keyof typeof STATUS]): string {
const map: Record<string, string> = {
[STATUS.PENDING]: "进行中",
[STATUS.SUCCESS]: "成功",
[STATUS.ERROR]: "失败",
};
return map[s];
}
readonly 和 as const 的区别:
// readonly:对象属性只读,但数组长度可变
interface Point {
readonly x: number;
readonly y: number;
}
const p: Point = { x: 1, y: 2 };
p.x = 3; // ❌
// as const:整个对象深度只读,数组长度也不可变
const point = { x: 1, y: 2 } as const;
// point.x = 3; // ❌
附录:常见面试追问表格
| # | 追问问题 | 参考答案 |
|---|---|---|
| 1 | TypeScript 的 tsconfig.json 中,哪些选项与类型检查最相关? | strict、strictNullChecks、noImplicitAny、strictPropertyInitialization |
| 2 | 什么是类型守卫(Type Guard)?举例说明 | typeof、instanceof、自定义守卫函数(如 isString(val: unknown)) |
| 3 | typeof 在 TypeScript 中有哪些用法? | 在类型上下文获取变量/值的类型:type A = typeof obj |
| 4 | keyof 的作用是什么? | 获取对象类型的所有键名组成的联合类型 |
| 5 | 什么是映射类型(Mapped Types)? | 基于已有类型,通过 [P in keyof T] 语法生成新类型,如 Partial<T>、Required<T> |
| 6 | infer 关键字的作用是什么? | 在条件类型中推断类型:type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never |
| 7 | 什么是装饰器(Decorator)?TS 如何启用? | 装饰器是实验性功能,需要在 tsconfig.json 开启 "experimentalDecorators": true,Angular 大量使用 |
| 8 | TypeScript 编译后的代码,类型信息还存在吗? | 不存在。TS 编译后生成纯 JS 代码,所有类型信息在编译时丢弃 |
| 9 | namespace 和 module 的区别? | namespace 是 TS 的模块组织方式,module 是 ES6 模块。两者的 import/export 语法可以互通 |
| 10 | 如何让 TS 支持 JSON 文件的导入? | 在 tsconfig.json 中配置 "resolveJsonModule": true,然后可以直接 import config from "./config.json" |
填空题自测
6. 枚举类型
6.1 TypeScript 枚举分为三种:数字枚举、字符串枚举、__ 枚举(用 const enum 声明)。常量枚举在编译后会被完全 __,没有任何运行时开销。
6.2 数字枚举如果不显式赋值,默认从 __ 开始递增;并且数字枚举具有 __ 特性,例如 Status[1] 可以拿到 "Pending"。
6.3 字符串枚举每个成员必须 __ 赋值(不能用默认值);典型应用是表示明确的业务含义,如 Environment.Development = "dev"。
6.4 实际项目中,后端返回的状态码适合用 __ 枚举;性能敏感的场景(如 CacheKey)适合用 __ 枚举。
参考答案
-
6.1
常量,消除 -
6.2
0,反向映射 -
6.3
手动 -
6.4
数字,常量
7. 泛型
7.1 泛型是类型的 __(参数化机制),用尖括号包裹(如 <T>),让同一段代码支持多种类型的同时保留 __。
7.2function identity<T>(arg: T): T { return arg; } 中,T 是 __(泛型参数 / 具体类型);调用时既可以显式写 identity<string>("hello"),也可以让 TS 自动 __。
7.3 使用 extends 关键字可以限制泛型范围,称为泛型约束。function longest<T extends { length: number }>(a: T, b: T): T 中,T 必须有 length 属性,否则 longest(1, 2) 会 __(编译报错 / 运行时崩溃)。
7.4K extends keyof T 表示 K 必须是 T 的 __ 组成的联合类型;function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] 的返回值 T[K] 表示 __。
参考答案
-
7.1
参数化机制(或变量),类型安全 -
7.2
泛型参数,推断 -
7.3
编译报错 -
7.4
键名,K 对应位置上的值类型
8. type 与 interface 定义函数类型的区别
8.1type Handler = (e: MouseEvent) => void; 与 interface Handler { (e: MouseEvent): void; } 在使用上 __(有区别 / 无区别)。
8.2interface 可以同时描述对象的属性和函数(call signature + 属性),type__(能 / 不能)这样写。interface 还支持 __,同名会自动合并。
8.3type 可以定义 __ 类型、__ 类型、__ 等复杂类型,这是 type 比 interface 更灵活的地方。
8.4 React 组件的 Props 类型通常用 interface ButtonProps { ... } 声明,这种场景下 type 也能写但 interface 更符合社区惯例。
参考答案
-
8.1
无区别 -
8.2
不能,声明合并 -
8.3
联合,交叉,元组(任意顺序)
9. null 与 undefined
9.1undefined 表示「这里 __ 值」,通常由 JavaScript __ 赋值(如变量声明未赋值);null 表示「这里 __ 值」,需要 __ 赋值。
9.2typeof undefined 的结果是 "undefined";typeof null 的结果是 "object",这是 JS 的 __。
9.3null == undefined 的结果是 true;null === undefined 的结果是 false。判断变量是否为 null 或 undefined,最简洁的写法是 value __________ null。
9.4tsconfig.json 中开启 __ 后,TS 不允许 string 类型隐式接受 null/undefined,必须显式声明 string | null 并做空值判断。
9.5inputName || "默认" 会把空字符串 "" 也替换成"默认",推荐改用 __ 运算符(??)。
参考答案
-
9.1
没有,自动,没有,手动 -
9.2
历史遗留 bug(或历史遗留问题) -
9.3
==(即value == null) -
9.4
strictNullChecks -
9.5
空值合并(或??)
10. 只读数组与 as const
10.1 TypeScript 提供三种只读数组声明方式:ReadonlyArray<number>、readonly number[] 和 __________。
10.2as const 断言后,数组元素类型被推断为具体的 __ 类型(如 "north" 而不是 string),整个数组深度只读。
10.3readonly 关键字 __(支持 / 不支持)对象属性的深度只读;as const__(支持 / 不支持)。
10.4 实际项目中,函数参数防止内部修改数组用 __(如 function sum(nums: readonly number[]));声明路由表、菜单、状态常量等配置用 __。
10.5const STATUS = { PENDING: "pending", SUCCESS: "success", ERROR: "error" } as const; 中,type Status = typeof STATUS[keyof typeof STATUS] 等价于 "pending" | "success" | "error",这是 as const 的 __ 效果。
参考答案
-
10.1
as const -
10.2
字面量 -
10.3
不支持,支持 -
10.4
readonly T[](或ReadonlyArray<T>),as const -
10.5
字面量收窄
附加题:综合应用(跨 Q6-Q10)
A1 路由配置 const ROUTES = { home: "/", about: "/about", user: "/user" } as const;,通过 type Route = typeof ROUTES[keyof typeof ROUTES] 推导出的联合类型是什么?这种写法的核心是利用了 as const 的哪两个特性?__
A2 给一个订单状态设计枚举,要求:后端返回数字 1-5 表示不同状态,前端用 switch 转换为中文。应该选 __ 枚举,理由是 __。
A3 封装一个泛型 request<T> 函数:传入用户 ID,返回 Promise<ApiResponse<User>>。请写出 ApiResponse<T> 的接口定义和 request 函数签名。__
A4 一个 React Hook 返回 [value, setValue],其中 value 可能是 null。请写出 Hook 的返回类型,应该用 __________ 来表示 value 可能为空的状态。
参考答案
-
A1
"/" | "/about" | "/user";核心利用as const的「字面量收窄」+「深度只读」两个特性 -
A2
数字枚举,理由是后端返回的就是数字(1-5),数字枚举天然支持且能反向映射查键名 -
A3 参考 Q7 示例代码(
ApiResponse<T>含code/data/message,request<T>(url): Promise<ApiResponse<T>>) -
A4
[T | null, (v: T | null) => void](即useState<T | null>)
辛苦了
,编编依旧给刻苦学习的你们,准备了开胃小菜
问:TypeScript 和 JavaScript 去面试,为什么 TS 没过?
答:面试官说它条条框框太多,太爱较真,相处起来一点不松弛


138

被折叠的 条评论
为什么被折叠?



