主题
附录 A1 · 手写题合集(30 道高频)
一句话开篇:手写题是前端面试的"基本功大考"。同样一段
Promise.all,能不能 30 行内手敲出来,直接区分初级、中级和高级前端。本章把 30 道高频手写题一次性梳理清楚——题目、思路、递进版本、复杂度、加分点全部到位。
0. 生活类比:手写题是"厨师颠勺"
把前端面试想象成厨师面试:
- 概念题(什么是闭包?)= 让你说清"煎炒烹炸"的定义
- 场景题(怎么设计权限系统?)= 让你描述一桌满汉全席怎么安排
- 手写题(手写一个 debounce)= 让你当场颠一下勺——动作变形不变形,一眼就能看出来
颠勺漂亮的厨师不一定能开餐厅,但开餐厅的大厨必然颠勺漂亮。
1. 面试为什么爱考手写题?
| 维度 | 手写题能暴露什么 |
|---|---|
| 基础扎不扎实 | 闭包、this、原型链、Promise 状态机这些概念,敢不敢、能不能落地成代码 |
| API 熟不熟 | 写 flat 不会用 concat、写 bind 不知道 Symbol,立马露怯 |
| 思维清不清晰 | 能不能从"简单版 → 完整版 → 进阶版"逐层递进,体现工程素养 |
| 边界考不考虑 | 防抖立即执行模式、深拷贝循环引用、Promise 多次 resolve、bind 配合 new ……都是基本盘 |
| 代码风不风骚 | 同样实现,有人 50 行写不完,有人 8 行 + 链式 + 注释清晰 |
一句话:概念题考"懂不懂",手写题考"做不做得到"。
2. 30 道高频题地图
按"考察知识点"分组,方便你针对性补短板:
┌─────────────────────────────────────────────────────────────────┐
│ 30 道高频手写题地图 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ ① 函数控制(5) │ │ ② 类型与原型(4) │ │
│ │ Q1 防抖 debounce │ │ Q5 手写 new │ │
│ │ Q2 节流 throttle │ │ Q6 手写 instanceof │ │
│ │ Q15 柯里化 curry │ │ Q7 手写 call │ │
│ │ Q16 组合 compose │ │ Q8 手写 apply │ │
│ │ Q24 sleep │ │ Q9 手写 bind │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ ③ 异步与 Promise(7) │ │ ④ 数据结构与算法(6) │ │
│ │ Q10 手写 Promise │ │ Q3 深拷贝 │ │
│ │ Q11 Promise.all │ │ Q4 浅拷贝 │ │
│ │ Q12 Promise.race │ │ Q18 数组扁平化 │ │
│ │ Q13 .allSettled │ │ Q19 数组去重 │ │
│ │ Q14 Promise.any │ │ Q20 isEqual │ │
│ │ Q25 asyncPool │ │ Q27 LRU Cache │ │
│ │ Q30 红绿灯 │ └──────────────────────┘ │
│ └──────────────────────┘ │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ ⑤ 工具函数(5) │ │ ⑥ 设计模式(3) │ │
│ │ Q17 EventEmitter │ │ Q26 LazyMan │ │
│ │ Q21 千分位 │ │ Q28 JSON.parse │ │
│ │ Q22 URL 参数解析 │ │ Q29 Object.create │ │
│ │ Q23 模板字符串 │ └──────────────────────┘ │
│ │ Q23 render │ │
│ └──────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘3. 高频题"出现频率"统计
综合阿里、字节、腾讯、美团、百度、京东、拼多多 2022—2025 面经统计:
| 频率 | 题目 |
|---|---|
| ⭐⭐⭐⭐⭐ | 防抖、节流、深拷贝、手写 Promise、Promise.all、bind、new |
| ⭐⭐⭐⭐ | call/apply、instanceof、柯里化、EventEmitter、扁平化、去重、asyncPool |
| ⭐⭐⭐ | compose、isEqual、千分位、URL 解析、Promise.race/allSettled/any、LRU |
| ⭐⭐ | sleep、模板字符串、Object.create、JSON.parse、红绿灯、LazyMan |
应聘建议:
- 校招/初级:⭐⭐⭐⭐⭐ 全部能 5 分钟内手写
- 社招中级:⭐⭐⭐⭐ 全部熟练,理解原理
- 社招高级/资深:30 道全部熟,能讲出"为什么这么写"和"边界 case"
4. 通用方法论:怎么写好一道手写题?
4.1 三段式答题法
每道题都按**「简单版 → 完整版 → 进阶版」**三段答:
┌─────────────┐ 能跑就行 ┌─────────────┐ 考虑边界 ┌─────────────┐
│ 简单版 │ ──────────→ │ 完整版 │ ──────────→ │ 进阶版 │
│ (~10 行) │ │ (~30 行) │ │ (50+ 行) │
└─────────────┘ └─────────────┘ └─────────────┘
核心逻辑 边界 case 工程优化示例:手写防抖
- 简单版:
setTimeout包一层就行 - 完整版:处理
this指向、参数透传、清除上一次定时器 - 进阶版:支持立即执行(leading)、取消(cancel)、Promise 化返回结果
4.2 答题套路(面试现场版)
"好的,这道题我会从三个层次来回答。"
- 先口述思路(30 秒):让面试官知道你脑子里有图
- 写简单版(1 分钟):先把核心跑通
- 加边界处理(2 分钟):边写边说"这里要注意 this、要注意空数组、要注意循环引用……"
- 写进阶版本(如果时间允许):体现工程素养
4.3 一定要说出口的几句话
| 话术 | 适用题目 | 加分原因 |
|---|---|---|
"我用 Symbol 避免属性冲突" | bind、深拷贝 | 体现严谨 |
"我用 WeakMap 处理循环引用,不会内存泄漏" | 深拷贝、isEqual | 体现对 GC 的理解 |
| "时间复杂度 O(n),空间 O(n)" | 几乎所有题 | 体现算法素养 |
"如果是大数组,建议用 for 循环不要用 forEach" | 数组类题 | 体现性能意识 |
"考虑一下 null、undefined、NaN 的边界" | 几乎所有题 | 体现工程素养 |
5. 学习路线建议
第一周:函数与原型基本盘
- Day 1-2:Q1 防抖 + Q2 节流(搞懂 setTimeout、闭包、this)
- Day 3-4:Q5 new + Q6 instanceof(搞懂原型链)
- Day 5-7:Q7/Q8/Q9 call/apply/bind(搞懂 this 绑定)
第二周:异步与 Promise 全家桶
- Day 1-3:Q10 手写 Promise(最重要,多写几遍)
- Day 4-5:Q11/12/13/14 Promise 静态方法
- Day 6-7:Q24 sleep + Q25 asyncPool + Q30 红绿灯
第三周:数据处理 + 工具函数
- Day 1-2:Q3/Q4 深浅拷贝 + Q20 isEqual
- Day 3-4:Q18 扁平化 + Q19 去重
- Day 5-7:Q15/Q16 柯里化、组合 + Q17 EventEmitter
第四周:综合 + 设计
- Day 1-2:Q21/Q22/Q23 工具函数三件套
- Day 3-4:Q26 LazyMan + Q27 LRU
- Day 5-7:Q28 JSON.parse + Q29 Object.create + 复习 + 模拟面试
6. 学习资源
配套文件
- 📖
qa.md:30 道题完整答案(题目 / 思路 / 递进代码 / 复杂度 / 加分点) - 💻
examples/01-debounce.js~examples/30-traffic-light.js:每道题独立可运行 - 🌐
demo/index.html:浏览器交互演示页
怎么用本章?
- 第一遍:先看
qa.md的题目,自己默写,写不出来再看答案 - 第二遍:对比
examples/下的代码,看你的实现差在哪 - 第三遍:打开
demo/index.html,实际跑一跑,体会输入输出 - 第四遍:模拟面试,对着白板/记事本,5 分钟内手写完整版
7. ⚠️ 易踩的坑(10 大经典翻车点)
| # | 坑 | 翻车率 |
|---|---|---|
| 1 | 防抖忘记返回函数,直接执行 | 90% |
| 2 | bind 没考虑配合 new 的情况 | 80% |
| 3 | 深拷贝没处理循环引用 | 70% |
| 4 | Promise 没实现"状态不可逆"(pending → resolve 后再 reject 失败) | 60% |
| 5 | call/apply 实现时用 obj.fn() 修改了原对象(没用 Symbol) | 60% |
| 6 | 扁平化用 flat() 而不是手写递归 | 50% |
| 7 | 节流的"前缘 + 后缘"边界没处理 | 50% |
| 8 | EventEmitter 的 once 实现错误(移除时找不到原函数) | 50% |
| 9 | Promise.all 任意一个 reject 后还在收集结果 | 40% |
| 10 | LRU 用对象不用 Map(无法保证遍历顺序) | 40% |
8. 一句话总结
手写题不是炫技,而是用最少的代码暴露你最深的功底。背诵答案不如理解原理,理解原理不如独立写出三个版本。
9. 延伸阅读
- TC39 ECMA-262 规范(Promise A+)
- MDN Function.prototype.bind
- 冴羽的博客 · 手写系列
- 配套:
qa.md(30 道题完整解答)/examples/(独立可运行代码)/demo/index.html(浏览器交互演示)