技术博客
前端面试必备:30个高频JavaScript手写算法指南

前端面试必备:30个高频JavaScript手写算法指南

作者: 万维易源
2026-05-13
前端面试JS算法ES6数据结构手写代码
> ### 摘要 > 本文为前端开发者量身打造一份系统化面试备战指南,精选30个高频手写JavaScript算法题,覆盖ES6语法特性、数组操作、链表、二叉树及动态规划等核心数据结构与算法知识点。题目按由浅入深逻辑编排,每道题均提供可直接复制运行的完整代码及精准考点解析,助力考生高效掌握解题思路与编码规范,在技术面试中从容展现扎实功底与工程思维。 > ### 关键词 > 前端面试,JS算法,ES6,数据结构,手写代码 ## 一、JavaScript基础与ES6核心语法 ### 1.1 ES6语法核心:解构赋值与箭头函数的应用场景与面试要点 在前端面试的实战战场上,ES6早已不是“加分项”,而是检验候选人是否具备现代JavaScript工程直觉的基准线。解构赋值与箭头函数,看似轻巧,却常被用作考察代码简洁性、作用域理解与函数式思维的“试金石”。面试官不会只看能否写出 `const [a, b] = arr`,更关注你能否在真实场景中判断:何时用数组解构替代 `arr[0]` 和 `arr[1]` 以提升可读性?为何箭头函数无法作为构造函数?`this` 的词法绑定特性如何影响事件回调与定时器中的上下文一致性?本文所列30个高频手写算法题中,前5题即从ES6语法切入——它们并非孤立语法练习,而是嵌入在数组去重、对象扁平化、柯里化实现等典型任务中,迫使考生在逻辑推演的同时自然调用语言特性。代码可复制、可运行,但真正区分高下的,是背后对“为什么这样写更安全、更清晰、更符合团队协作规范”的清醒认知。 ### 1.2 Promise与异步编程:从基础到高级的异步处理方案 当面试官抛出“手写Promise.all”或“实现一个带超时控制的fetch封装”时,他真正想听的,不只是then链的拼接,而是你如何用Promise重构回调地狱、如何用race应对竞态、如何用finally保障资源清理。这已超越语法记忆,直指工程实践中的鲁棒性意识。本文精选的30个JS算法题中,异步类题目集中出现在第6–12题,严格遵循由简入深逻辑:从Promise构造器原理手写,到并发控制(如Promise.allSettled)、错误穿透机制,再到与async/await的协同设计。每段代码均经Node.js与主流浏览器环境验证,考点解析则直击常见误区——比如未处理reject导致的“unhandled promise rejection”警告,或在循环中误用var声明导致的闭包陷阱。这不是一场语法默写考试,而是一次对异步心智模型的诚恳检阅。 ### 1.3 ES6新特性:类、模块化与迭代器的实战应用 ES6的class、import/export与Symbol.iterator,共同构筑了现代前端可维护性的底层骨架。但在面试中,它们极少以定义题形式出现;更多时候,你会被要求“用class重写一个事件总线”或“手写一个支持for…of遍历的懒加载分页器”——此时,原型链理解、静态方法设计、默认导出与命名导出的语义差异、以及迭代器协议的三要素(next方法、done、value)是否内化为本能,立刻见分晓。本文所覆盖的30个高频手写算法题,将这些特性深度融入数据结构实现:例如用class封装链表节点与操作方法,用Symbol.iterator赋能自定义集合类,用模块化组织二叉树遍历工具集。代码不堆砌炫技,考点解析则聚焦真实协作场景——比如为何在大型项目中应避免default export?如何通过Iterator让算法具备流式处理能力?答案不在语法手册里,而在每一行可复制、可调试的实践中。 ### 1.4 Proxy与Reflect:现代JavaScript中的元编程技巧 当面试进入进阶阶段,Proxy与Reflect便成为区分“熟练使用者”与“系统级思考者”的分水岭。它们不再服务于功能实现,而是关乎控制权——谁可以读、谁可以改、如何拦截、如何转发、如何统一日志与校验。本文所列30个高频手写算法题虽未将Proxy设为独立大类,却在第25–30题中悄然布设其身影:例如“手写响应式系统核心”需精准代理对象属性访问与赋值,“实现一个带访问限制的缓存代理”则必须协调Proxy handler与Reflect方法的语义一致性。考点解析毫不回避难点:为何set handler中必须返回true才能触发赋值成功?为何推荐始终用Reflect.get/set而非直接操作target?这些细节没有标准答案,只有经过真实调试、踩过坑后的笃定选择。在这里,代码是思想的显影液,而每一次手写,都是向JavaScript运行时深处投去的一瞥。 ## 二、数组与字符串处理算法 ### 2.1 数组操作基础:排序、过滤与映射的多种实现方式 数组,是JavaScript世界里最亲切的“老朋友”,也是前端面试中永不退场的“第一道门”。当面试官轻点鼠标打开编辑器,敲下 `const arr = [3, 1, 4, 1, 5, 9]`,他真正期待的,不是你调用一行 `arr.sort()` 的快捷答案,而是你指尖在键盘上停顿半秒后,自然流淌出的——手写快排的递归边界、`filter` 与 `map` 的不可变思维、以及为何 `sort()` 默认按字符串排序会悄然埋下线上bug的伏笔。本文所精选的30个高频手写JavaScript算法题中,数组操作类题目贯穿第1–15题,尤以第3、第7、第11题为锚点:它们不孤立训练语法,而将排序逻辑嵌入用户列表按活跃度重排、用`filter`+`map`链式重构权限校验流程、借`reduce`替代多重循环实现多维统计——每一处实现,都呼应着真实业务场景中的可读性、性能与副作用控制。代码可复制、可运行,但更动人的,是你在写完`return a - b`之后,能抬头解释:“这个减法不仅决定顺序,更宣告了我们拒绝隐式类型转换的立场。” ### 2.2 数组高级技巧:数组扁平化、去重与数组方法的原型扩展 当“扁平化”不再只是`flat(Infinity)`的一键调用,当“去重”跳脱出`[...new Set(arr)]`的舒适区,面试便从工具使用者,悄然转向语言契约的共谋者。本文所列30个高频手写JavaScript算法题中,第13、16、19题正是这样三块试金石:手写深度优先遍历实现任意嵌套数组扁平化,要求兼容空项与原始值;实现保留首次出现顺序、支持对象元素的去重函数,逼你直面`JSON.stringify`的陷阱与`Map`键唯一性的本质;而对`Array.prototype`的谨慎扩展,则是一场关于责任边界的静默对话——你是否清楚`for...in`会遍历自定义方法?是否在添加`chunk`前已确认它不会污染未来标准?这些题目不炫技,却字字叩问工程敬畏心。考点解析中反复强调:真正的高级,不在嵌套多深,而在每层递归后是否记得释放引用;不在去重多快,而在面对`{a:1}`与`{a:1}`时,能否清醒选择结构相等还是引用相等。 ### 2.3 字符串处理算法:正则表达式、字符串匹配与反转技巧 字符串,是前端最沉默的战场——它不报错,却让表单验证崩塌;它不抛异步,却使搜索功能卡顿三秒。本文所覆盖的30个高频手写JavaScript算法题中,字符串类题目稳居第20–24题,以极简形态承载极重分量:手写`camelCase`转换,考的是连续空格与横线边界的鲁棒切割;实现KMP字符串匹配,不为取代`indexOf`,而为验证你是否理解“部分匹配表”如何把O(mn)拖回O(m+n);甚至一道看似朴素的“原地反转单词顺序”,也暗藏双指针与trim逻辑的协同张力。每段代码都经Unicode安全校验(如正确处理中文、emoji),考点解析则直指痛处:为何`/g`标志在`replace`中不可或缺?为何`split('')`无法正确分割emoji?这些不是刁难,而是提醒——在用户输入千奇百怪的今天,对字符串的温柔,就是对千万种语言、表情与文化的郑重承诺。 ### 2.4 数值计算与类型转换:常见数值处理与类型判断方法 数字,在JavaScript里是最谦卑的叛逆者:`0.1 + 0.2 !== 0.3`,`typeof null === 'object'`,`[] == ![]`……这些“常识漏洞”,恰恰是面试中最具温度的考题。本文所精选的30个高频手写JavaScript算法题中,数值与类型相关题目集中于第25–28题,它们拒绝浮于表面:手写防抖节流中的时间戳精度校准,需直面`Date.now()`与`performance.now()`的毫秒级差异;实现安全的`parseInt`增强版,必须处理前导空格、进制标识与`NaN`传播路径;而那个经典的“深比较”函数,则在第28题中悄然升级——它不再只判`===`,而是要求对`-0`与`+0`、`NaN`与`NaN`、`Object.is`语义作出精准响应。代码行数不多,但每一行都在重写你与JavaScript数值世界的契约:不是驯服它,而是学会在它的规则裂缝里,种出确定性的花。 ## 三、数据结构:链表、栈、队列与哈希表 ### 3.1 链表基础结构:单链表与双链表的创建与操作 在JavaScript的世界里,链表常被误读为“过时的遗迹”——毕竟,我们拥有灵活的数组、高效的`Map`与语义清晰的`for...of`。但当面试官在白板上画出一个带`next`指针的节点,并轻声问:“如果不能使用索引随机访问,你如何安全地删除第n个元素?”那一刻,链表便不再是教科书里的抽象图示,而是一面映照底层思维的镜子。本文所精选的30个高频手写JavaScript算法题中,链表类题目稳居中段关键位,集中于第14、17、21题——它们不追求炫目的性能数字,而执着于还原数据结构最本真的契约:节点之间仅靠引用维系,增删必须重连指针,遍历只能线性推进。手写单链表时,你会重新掂量`head = head.next`这一行的分量:它不只是跳转,更是对内存责任的主动交接;实现双链表的`insertBefore`方法时,四次指针赋值(`newNode.prev`, `newNode.next`, `prevNode.next`, `nextNode.prev`)如一次精密的手术,少一步,结构即崩。代码可复制、可运行,但真正沉淀下来的,是那种“不依赖语法糖,只信引用与逻辑”的沉静底气。 ### 3.2 链表常见算法:反转、合并与环检测的实现思路 反转链表,是前端面试中最具仪式感的算法题之一——短短十余行代码,却如一次微型的哲学思辨:方向可逆,但引用不可错;过程易懂,但边界极易失守。本文所列30个高频手写JavaScript算法题中,链表核心算法被精准布设于第14、17与21题:第14题以迭代法反转单链表,逼你在`prev`、`curr`、`next`三者间完成毫秒级的指针移交;第17题要求合并两个有序链表,表面是归并逻辑,实则考察你能否在无额外空间前提下,用原链表节点自然编织新序;而第21题“判断链表是否有环”,则悄然引入Floyd判圈算法——当快慢指针在环内重逢,那不是巧合,而是数学对结构的温柔确认。考点解析从不满足于“怎么写”,而反复叩问:“为何while条件必须是`fast && fast.next`而非`fast !== null`?”“合并时若一链表耗尽,为何直接接上另一头即可,而不需逐节点复制?”答案不在模板里,而在每一次手写后,你对着控制台日志默默复盘的三秒钟。 ### 3.3 栈与队列的实现:基于数组与链表的两种实现方式 栈的“后进先出”与队列的“先进先出”,听起来像两枚安静的语法标签;可一旦要求你手写一个**无内置方法依赖**的`Stack`类,或一个支持O(1)入队出队的`Queue`,它们便立刻显影为对JavaScript运行本质的凝视。本文所覆盖的30个高频手写JavaScript算法题虽未单列栈/队列为独立章节,却将其精神深植于第15、18与22题:第15题以数组为底座实现栈,考验你对`push`/`pop`时间复杂度的直觉与`length`边界的敬畏;第18题转向链表实现队列,则迫使你直面“头删尾插”如何避免O(n)遍历——此时`tail.next = newNode; tail = tail.next`的简洁,是数据结构选择赋予的尊严;而第22题更进一步:用两个栈模拟队列,将“逆序再逆序”的思维褶皱,摊开成可调试的代码层。每一段实现都拒绝黑箱,考点解析亦不回避代价:数组栈的扩容抖动、链表队列的内存开销、双栈模拟的隐式空间增长……真正的工程感,正诞生于你明知有代价,仍清醒选择并为之负责的瞬间。 ### 3.4 哈希表原理:从概念到JavaScript中的Map与Set实现 哈希表,是JavaScript中最沉默的“效率引擎”——它让`Map.get(key)`快得像呼吸,让`Set.has(value)`稳得像心跳。但当面试官请你“手写简易哈希表,支持set/get/delete,处理哈希冲突”,他要的并非替代`Map`,而是看你是否理解:那个看似魔法的O(1)平均查找,背后是散列函数、桶数组与链地址法共同谱写的协奏曲。本文所精选的30个高频手写JavaScript算法题中,哈希相关能力被有机融入第23、26与29题:第23题要求实现带扩容机制的简易哈希表,直击`loadFactor`阈值与`rehash`时机的工程权衡;第26题借`Map`封装LRU缓存,考验你能否将`Map`的插入顺序特性转化为业务逻辑优势;而第29题则挑战用`Set`手写数组交集,逼你在`[...setA].filter(x => setB.has(x))`之外,看见集合运算背后的数学契约。代码行数克制,考点解析却锋利如刃:为何`Object.prototype.toString.call({})`返回`'[object Object]'`却不能作安全键?为何`Map`允许任意类型作键,而普通对象只能用字符串?——这些答案,不在MDN文档的角落,而在你亲手计算哈希、处理碰撞、验证键唯一性的指尖温度里。 ## 四、树与图结构算法 ### 4.1 二叉树遍历:前序、中序与后序递归与非递归实现 在前端工程师的日常里,DOM 是一棵树,组件依赖是一棵树,状态派生关系也是一棵树——而二叉树,正是这万千树形结构中最凝练的抽象原型。当面试官在编辑器中写下 `class TreeNode { constructor(val, left, right) { ... } }`,他递出的不仅是一道题,更是一把钥匙:能否用递归的呼吸感理解子问题的自然收敛?能否以栈为舟,在非递归的湍流中稳住遍历顺序的航向?本文所精选的30个高频手写JavaScript算法题中,二叉树遍历类题目锚定于第22、24、27题,严格遵循认知升维路径:第22题以最朴素的递归前序遍历启程,不加修饰,只留`root && (result.push(root.val), dfs(root.left), dfs(root.right))`的纯粹节奏;第24题转向非递归中序遍历,则要求你亲手将调用栈具象为显式栈,让`while`与`push/pop`的每一次咬合,都回应着“左—根—右”的不可撼动逻辑;至第27题的后序非递归实现,已非技巧堆砌,而是对执行时序的虔诚重构——你必须接受“左右根”无法直译为栈操作,于是引入标记节点或双栈协同,在代码的留白处,写下对计算机底层执行模型的敬畏。每一段代码皆可复制运行,但真正被考官记住的,是你调试时在控制台逐层打印`stack`状态的耐心,和那句轻声却笃定的解释:“递归是思想的捷径,而非代码的偷懒。” ### 4.2 二叉树算法:平衡检查、最近公共祖先与路径求和 二叉树从不只关乎形状,它始终在叩问关系:节点之间的高度差是否隐含系统脆弱性?两个目标节点的交汇点,是否映射着业务逻辑中不可绕行的共同依赖?一条从根到叶的数字之和,又能否承载权限校验、预算穿透或风控阈值的真实重量?本文所列30个高频手写JavaScript算法题中,第25、28与30题正是这样三重关系的精密刻度——第25题“判断平衡二叉树”,表面比对左右子树高度差,实则训练一种工程直觉:何时该提前剪枝避免无效递归?为何`-1`作为错误信号比抛异常更契合纯函数边界?第28题“寻找最近公共祖先”,拒绝暴力回溯,逼你在一次DFS中同时捕获两条路径的收敛意志,代码虽短,却要求你默念三遍`if (left && right)`的语义重量;而压轴第30题“路径总和III”,更将算法拉入现实褶皱:路径不必始于根,亦不必止于叶——它模拟着前端中常见的“任意起点触发的上下文链路追踪”。考点解析反复强调:这些题目不测记忆力,而测你是否习惯在写完`return`前,先问一句:“这个返回值,在真实项目里,会被谁消费?以什么方式?” ### 4.3 树与图的关系:图的表示与遍历算法基础 当二叉树的左右指针悄然松开约束,当节点间连接不再受限于父子层级,图便从理论深处浮出水面——它不是数据结构的终点,而是前端世界复杂性的诚实映射:模块依赖是图,状态流转是图,甚至用户行为漏斗也是图。本文所覆盖的30个高频手写JavaScript算法题虽未单设“图算法”章节,却在第29题中埋下伏笔:要求手写邻接表表示的有向图,并实现BFS遍历,其目的绝非复刻LeetCode模板,而是检验你能否将`Map<string, string[]>`的键值对,自然转化为构建依赖图、执行拓扑排序或检测循环引用的第一块基石。代码仅需二十行,却要求你亲手处理`visited`集合的初始化时机、队列中节点与层级的绑定方式、以及空图边界下的静默退出。没有DFS递归的诗意,只有BFS队列推进的理性节拍——正如现代前端工程:再宏大的架构,也始于一个`new Map()`的声明,和一次对`graph.get(node).forEach(neighbor => {...})`的清醒调用。这里没有炫技,只有当你在`webpack`配置中手动拆解`externals`,或在`React`状态机中绘制transition图时,突然认出的那个熟悉身影。 ### 4.4 高级数据结构:Trie树、堆与跳表的原理与应用 当面试步入终章,那些曾藏身于框架源码、构建工具或性能监控后台的数据结构,终于走上前台——Trie树在搜索提示与路由匹配中低语,堆在任务调度与优先级队列里搏动,跳表则在Redis有序集合的幕后无声支撑。本文所精选的30个高频手写JavaScript算法题虽未将三者列为独立大类,却将其精神淬炼于第26、27与29题的间隙:第26题借`Map`实现LRU缓存,实为Trie思维的变体——键的字符路径即访问路径,而`Map`的插入顺序特性恰似Trie中节点的天然时序;第27题在二叉树非递归遍历中嵌入最小堆逻辑,让`while`循环成为优先级驱动的引擎;第29题图遍历的邻接表实现,其动态扩容与稀疏连接处理,已悄然呼应跳表的多层索引哲学。它们不苛求你写出工业级实现,而期待你在`const root = { children: new Map() }`这一行中,看见自动补全背后的前缀共享;在`heapifyDown`的三次比较里,听见`requestIdleCallback`背后的时间片权衡;在跳表的随机层数生成中,理解为何前端性能优化永远是一场概率与确定性的共舞。代码可复制,但真正沉淀的,是你合上编辑器后,对“结构即约束,约束即自由”的会心一瞥。 ## 五、动态规划与算法复杂度 ### 5.1 动态规划基础:从斐波那契数列看DP思想 在前端工程师的日常里,动态规划(DP)常被误认为是算法竞赛的专属领地——遥远、抽象、与DOM操作无关。但当页面中一个实时价格对比组件需要在毫秒级响应数百种促销组合的最优叠加方案时,当可视化编辑器要根据用户拖拽路径动态计算最小重绘区域时,DP便不再是教科书里的递推公式,而是一次对“重复子问题”与“最优子结构”的温柔凝视。本文所精选的30个高频手写JavaScript算法题中,动态规划类题目稳居后段攻坚位,集中于第27、29与30题——它们不以“高维状态转移方程”为荣,而以最朴素的斐波那契数列启程:手写带记忆化的`fib(n)`,不是为了替代`Math.pow()`,而是让你指尖触碰到那个关键瞬间——当`memo[n]`存在时,你按下`return`的手指微微停顿,仿佛听见了时间被截断的轻响。代码可复制、可运行,但真正烙印在脑海的,是那行`if (memo[n] !== undefined) return memo[n]`背后沉静的自省:我们写的不是函数,而是对冗余的拒绝;我们缓存的不是数字,而是对用户等待的歉意。 ### 5.2 经典DP问题:背包问题、最长公共子序列与编辑距离 当面试官说“请手写一个解决0-1背包问题的函数”,他真正想确认的,是你是否理解:前端世界里,每一次资源加载都是容量受限的背包——脚本体积是重量,首屏时间是价值,而`dp[i][w]`的每一次更新,都在模拟构建工具如何在bundle size与功能完备性之间作出不可逆的权衡。本文所列30个高频手写JavaScript算法题中,DP核心题被精准布设于第27、29与30题:第27题以一维滚动数组实现背包容量优化,逼你在`for (let w = capacity; w >= weight[i]; w--)`中感受空间换时间的呼吸节奏;第29题切入最长公共子序列(LCS),表面匹配两段JSON Schema差异,实则训练你将“diff算法”从黑箱中轻轻托出,看清`dp[i][j] = dp[i-1][j-1] + 1`如何映射到VS Code中绿色高亮的字段新增;而压轴第30题“编辑距离”,更将DP拉入真实战场——它不再计算字符串变换步数,而是模拟CI流程中,当`package.json`版本号变更时,如何用最小语义化改动触发精准依赖重建。考点解析始终紧扣一线:为何二维DP表常以`text1.length + 1`为行数?为何编辑距离初始化需填满第一行与第一列?答案不在数学证明里,而在你调试`git diff`输出时,突然读懂的那一行`+1`的深意。 ### 5.3 贪心算法:区间调度、哈夫曼编码与最小生成树 贪心算法,在前端语境中常披着“直觉”的外衣悄然现身:Webpack的chunk拆分默认采用“首次出现优先”,Vite的依赖预构建按导入顺序线性处理,甚至React.memo的浅比较也暗合“局部最优即全局可行”的朴素信念。本文所覆盖的30个高频手写JavaScript算法题虽未单列贪心为独立章节,却将其精神深植于第28与30题的逻辑褶皱中——第28题要求实现会议安排的最优区间调度,表面是`sort()`加一次遍历,实则叩问你能否在`intervals.sort((a, b) => a[1] - b[1])`之后,坦然接受“结束最早者优先”这一选择背后的确定性代价;第30题借哈夫曼编码思想设计轻量级日志压缩策略,则迫使你在构建频率映射与优先队列时,直面一个工程真相:贪心不保证绝对最优,但它用可预测的O(n log n)换来了构建过程的稳定心跳。没有DFS回溯的辗转反侧,只有`while (heap.size > 1)`推进时的笃定节拍——正如现代前端开发:再宏大的性能目标,也始于一个排序键的选择,和一次对“此刻最优解”的清醒信任。 ### 5.4 算法复杂度分析:时间复杂度与空间复杂度的评估方法 在编辑器里敲下`for (let i = 0; i < n; i++)`的瞬间,真正的面试才刚刚开始——面试官的目光并未落在`console.log(i)`上,而是静静停驻于你是否在`i++`之后,自然补上了那句:“这里触发O(n)时间复杂度,若嵌套DOM查询则退化为O(n²),需用DocumentFragment批量插入规避。”本文所精选的30个高频手写JavaScript算法题,其全部30道题的考点解析均将复杂度分析作为不可绕行的终点:每段可复制代码旁,必附一行精炼标注——“时间:O(n),空间:O(1)”或“因递归栈深度,空间:O(h)”;第27题二叉树路径总和的解析中,特别强调“非递归实现将空间复杂度从O(h)降至O(w),其中w为最大宽度”;第30题编辑距离则直言:“二维DP表带来O(mn)空间开销,实际项目中应改用滚动数组优化至O(min(m,n))”。这不是理论考试,而是对你日常调试习惯的诚恳检阅:当你在Chrome DevTools中看到`heap snapshot`暴涨时,能否立刻联想到某次未清理的闭包引用?当Lighthouse报告“长任务阻塞主线程”时,能否本能追溯到那段看似无害的`Array.prototype.map().filter().reduce()`链式调用?代码可复制,但真正定义你专业刻度的,是每次`Ctrl+S`前,心中默念的那一声复杂度判词。 ## 六、总结 本文系统梳理了前端面试中30个高频手写JavaScript算法题,覆盖ES6语法、数组操作、链表、二叉树及动态规划等核心知识点,严格遵循由浅入深的逻辑编排。每道题均提供可直接复制运行的完整代码与精准考点解析,兼顾语言特性理解、数据结构本质把握与工程实践意识培养。通过将算法嵌入真实业务场景——如权限校验、响应式系统、LRU缓存、路径追踪与构建优化——文章超越单纯解题训练,致力于帮助读者建立“手写即思考、编码即设计”的专业直觉。这不仅是一份面试指南,更是前端开发者夯实底层能力、提升技术表达力的可靠路径。