深浅色
JS 与 TypeScript(背诵版)
1. var / let / const 与暂时性死区
- var:函数作用域、变量提升、可重复声明;let/const:块级作用域。
- TDZ(暂时性死区):声明前访问 let/const 直接抛 ReferenceError。
- const 约束的是绑定不可变,不是值不可变(对象属性仍能改)。
- 循环里 var 共享同一个变量(闭包陷阱),let 每轮新建绑定。
- 追问:typeof 一个 TDZ 中的变量?-> 抛错;typeof 未声明的变量才返回 undefined。
2. 原型链 / new / instanceof
- 对象通过
__proto__指向构造函数的 prototype,逐层向上直到 Object.prototype 再到 null。 - new 四步:创建空对象 -> 原型指向 F.prototype -> 以它为 this 执行构造函数 -> 返回值是对象就用它,否则用新对象。
- instanceof:沿原型链查找构造函数的 prototype 是否出现。
- 追问:class 与构造函数的区别?-> class 不能当普通函数调用、有 TDZ、方法不可枚举、默认严格模式。
3. 闭包与内存泄漏
- 闭包 = 函数 + 它捕获的词法环境,被捕获的变量无法回收。
- 泄漏场景:定时器 / 事件监听持有大对象、全局缓存、DOM 引用未释放、闭包持有旧 state。
- 追问:闭包变量存在哪?-> 堆上的环境记录,只要还被引用就不回收。
4. this 绑定
- 优先级:new > 显式绑定(call/apply/bind)> 隐式绑定(obj.fn())> 默认绑定(严格模式 undefined)。
- 箭头函数没有自己的 this,取定义时外层,且 call/bind 改不了。
- 追问:把 obj.fn 赋给变量再调用,this 是谁?-> 默认绑定,因为丢失了调用位置。
5. 事件循环
- 浏览器:执行栈清空 -> 清空所有微任务 -> 取一个宏任务 -> 再清微任务 -> 可能渲染。
- 宏任务:script、setTimeout、setInterval、I/O、UI 渲染;微任务:Promise.then、queueMicrotask、MutationObserver。
- Node 分阶段(timers -> poll -> check),process.nextTick 优先于 Promise。
- 追问:为什么 Promise 比 setTimeout 先执行?-> 微任务队列在每个宏任务之后被清空。
6. Promise 与 async / await
- 三态:pending / fulfilled / rejected,状态不可逆。
- then 返回新 Promise 实现链式;catch 捕获链上任意错误;finally 不改变结果。
- async 函数返回 Promise;await 是 then 的语法糖,会让出执行权(走微任务)。
- 多个 await 是串行的,要并行用
Promise.all;try/catch 能捕获 await 的 rejection。 - 追问:await 一个非 Promise 值?-> 包成 resolved Promise,仍让出一次微任务。
7. 深拷贝
- JSON 方案的坑:丢 undefined / function / Symbol、Date 变字符串、循环引用报错、NaN 变 null。
structuredClone:原生、支持循环引用和 Date/Map/Set,但不支持函数和类实例。- 手写:递归 + 类型判断 + WeakMap 记录已拷贝对象解决循环引用。
- 追问:浅拷贝有哪些?-> Object.assign、展开运算符、slice、concat。
8. TS 泛型 / 条件类型 / infer
- 泛型:
function f<T>(x: T): T;约束T extends { id: number }。 - 条件类型
T extends U ? X : Y;infer 用来提取类型,如 ReturnType 用infer R取出返回值类型。 - 常用工具类型:Partial / Required / Pick / Omit / Record / Exclude / Extract。
- 追问:any 和 unknown 的区别?-> unknown 更安全,必须先收窄才能用。
9. TS 运行时校验
- TS 类型编译后全部擦除,运行时拿不到任何类型信息。
- 方案:zod / valibot 定义 schema,用
z.infer反推类型,一份定义两处用。 - 也可以用类型守卫(
is函数)手写收窄。 - 追问:为什么接口返回值也要校验?-> 外部数据不可信(后端改字段、网关注入),类型断言只是自欺欺人。
10. ESM 与 CJS
- CJS:require 同步加载、运行时解析、导出值是拷贝;ESM:静态分析、编译期确定依赖、live binding(引用)、支持 tree shaking。
- 循环依赖:CJS 拿到未完成的 exports(可能 undefined);ESM 有提升,函数声明可用但初始化顺序要小心。
- 追问:Node 里能互相 require / import 吗?-> ESM 可 import CJS;CJS require ESM 在新版本 Node 才支持。
11. 常考手写
- call / apply:把函数挂到 context 上调用再删掉;bind 返回函数且支持 new。
- 防抖:n 毫秒内只执行最后一次;节流:n 毫秒内最多执行一次。
- Promise.all:计数 + 结果数组,任一 reject 整体 reject。
- 追问:防抖和节流的区别?-> 防抖是「等你停下来」,节流是「固定频率放行」。