这篇主要解决一个实际问题:当你面对对象、数组、字典和循环时,应该用哪种写法,为什么。
?? 和 || 的区别
?? 只在值为 null 或 undefined 时使用默认值。
const value = input ?? "default";|| 会在所有假值时使用默认值,包括 false、0、""、NaN、null、undefined。
const page = query.page || 1;如果 0 是合法值,不要用 || 做默认值。
const discount = userDiscount ?? 0;Record 字典
Record<K, V> 表示一个 key-value 映射。
const userAges: Record<string, number> = {
Alice: 25,
Bob: 30,
};如果 key 是固定集合,可以用联合类型限制。
type Role = "admin" | "editor" | "viewer";
const permissions: Record<Role, string[]> = {
admin: ["read", "write", "delete"],
editor: ["read", "write"],
viewer: ["read"],
};这比 Record<string, string[]> 更安全,因为少写一个 role 会直接报错。
索引签名
索引签名适合 key 完全动态的对象。
interface ScoreMap {
[name: string]: number;
}
const scores: ScoreMap = {};
scores.Alice = 95;缺点是它允许任意字符串 key,所以约束比联合类型弱。
循环怎么选
for 适合需要索引、需要提前退出、需要精细控制循环条件的场景。
for (let i = 0; i < items.length; i++) {
if (items[i] === target) break;
}for...of 适合遍历数组、字符串、Set 等可迭代对象,直接拿到值。
for (const fruit of fruits) {
console.log(fruit);
}for...in 适合遍历对象 key,但要注意原型链属性。
for (const key in person) {
if (Object.prototype.hasOwnProperty.call(person, key)) {
console.log(key, person[key as keyof typeof person]);
}
}forEach 与 map
forEach 用来执行副作用,不返回新数组。
numbers.forEach((num) => {
console.log(num);
});map 用来转换数组,并返回新数组。
const doubled = numbers.map((num) => num * 2);简单规则:
- 要生成新数组:用
map - 要过滤数组:用
filter - 要累计结果:用
reduce - 只是打印、调用函数、写日志:用
forEach - 需要
break或continue:用for...of或for
延伸理解
复习这篇时,不要只记住名词,要把重点放在 空值合并、Record、索引签名、循环选择、数组转换和副作用边界。这类知识如果只停留在定义层面,很容易在面试或项目中答得很散。更好的理解方式是把它放进一个具体场景:谁在调用它,输入从哪里来,失败后谁负责恢复,数据或状态会不会被重复处理。
- TypeScript 的价值是把运行时不确定性尽量前移到开发阶段,但它不能替代运行时校验。
- 类型设计应优先描述业务约束,而不是为了炫技使用复杂泛型。
- 遇到第三方输入、JSON、表单和 API 响应时,要结合类型收窄或 schema 校验。
在真实项目中,可以把它当成一个判断框架:先确认输入、约束、失败场景和可观测性,再决定具体工具或写法。 如果一个方案看起来很简单,要继续追问它在规模扩大、权限变化、异常恢复和团队协作下是否仍然成立。
实践检查清单
- 明确这个知识点在系统中的位置:是开发时约束、运行时能力、基础设施能力,还是协作流程。
- 写出一个最小可运行例子,并补一个失败例子;只会写 happy path 说明理解还不够稳。
- 记录常见误用:例如边界条件、权限假设、性能假设、同步/异步差异或环境差异。
- 把概念和项目经历关联起来:如果面试被追问,可以用自己的项目说明为什么这样选。
- 最后用一句话总结取舍:它牺牲了什么,换来了什么。
自测问题
- 这个主题解决的核心问题是什么?
- 如果不用当前方案,还有哪些替代方案?代价是什么?
- 最容易出错的边界条件在哪里?
- 如何在代码、测试或监控中验证它真的可靠?
项目化应用场景
可以把这类知识放到一个前后端共享类型的项目里理解:API 返回 JSON,前端先用 schema 做运行时校验,再把校验后的数据交给 TypeScript 类型系统。类型可以帮助编辑器提示和编译期检查,但不能保证网络返回一定正确。好的类型设计应该表达业务约束,例如角色、状态、权限、请求参数和响应结构,而不是所有地方都写成 string 或 any。
常见误区:
- 把 TypeScript 类型当成运行时校验。
- 为了省事使用 any,导致错误延后到线上。
- 泛型过度复杂,让调用者难以理解。