ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

搜狗前端秋招编程题全解析:考点拆解与避坑实战指南

搜狗前端秋招编程题全解析:考点拆解与避坑实战指南 秋招季又到了群里天天有人刷“搜狗2019秋招前端工程师编程题合集第一场”问得最多的一句话就是“这些题到底怎么练”。说实话前端岗位的笔试和后端、算法岗不太一样它既考 JavaScript 基础又考代码风格还考你在浏览器和编辑器之外的“裸写代码”能力。很多同学刷完这套题后的反应很一致题目看着都认识一提交就超时或者边界没处理好。这篇东西我就以这套秋招编程题为引子把前端笔试中真正决定成败的细节、考点和踩坑记录一次性讲透。不管你是正在准备校招还是打算跳槽试试水只要目标岗位带“前端”两个字这篇文章都值得你花十分钟看完。需要先说明一下我手头并没有搜狗那次笔试的官方原题本文所有分析都是基于历年搜狗前端笔试高频题型、以及同级别互联网公司秋招编程题的共性问题来还原和拆解。核心目的是帮你建立起一套应对前端在线编程题的完整方法论而不是单纯背答案。1. 从笔试说起前端编程题到底在筛什么很多人对前端笔试有个误解觉得前端不就是写页面嘛笔试最多考考 DOM 操作、CSS 布局。实际上到了搜狗这个体量的公司前端工程师的笔试已经非常接近“数据结构 JavaScript 语言特性 工程思维”的三合一考察纯切图和调样式的题目反而极少出现。为什么这么考原因很实际前端团队要维护的是整个 Web 应用的用户交互层这个层级的代码量和复杂度并不比后端低。页面状态管理、接口数据的格式化、列表渲染的性能优化、组件之间的通信每一项都依赖扎实的编程基本功。而那些编程题本质上是把日常开发中要用到的“数组处理”、“字符串解析”、“异步流程控制”等能力用一种更抽象、更集中的方式考出来。所以如果你想在这场笔试里拿到一个能进面试的分数光会 Vue 或 React 的 API 是不够的你必须能在不依赖任何框架、不依赖任何调试工具的情况下用纯 JavaScript 写出健壮、高效、可读的代码。这也是为什么所有大厂前端笔试都采用“核心代码模式”或“ACM 模式”的原因——他们要筛选的是真正理解语言和算法的人而不是只会复制粘贴方案的人。从筛人逻辑来看这套题的分层意图很明显第一题通常是比较简单的字符串或数组操作目标是让 60% 以上的人能做出来保证大家都有基本的参与感中间几题开始加大难度加入边界条件、时间复杂度的要求区分出基础扎实的人最后一两题往往涉及递归、动态规划或者复杂的数据处理目的就是筛出那批真正有算法功底和抗压能力的候选人。这就给了我们一个非常实用的复习方向不要一上来就死磕难题先保证简单题和中等题的完成速度和准确率。笔试是有时间限制的一道题卡太久后面能拿的分就没有了。2. 考核维度与命题思路还原一套前端编程题的完整框架2.1 为什么前端岗位的编程题几乎都是 JavaScript把这套题的名字拆开看“前端工程师”四个字已经限定了语言维度。后端可以用 Python、Java、Go 任选前端编程题则几乎默认 JavaScript偶尔会有 TypeScript 选项但核心逻辑还是那套。JavaScript 这门语言的特殊性决定了它作为笔试语言时有一些独有的考察点。比如原型链、闭包、事件循环、this 指向、数组的变异方法与非变异方法这些都是后端语言不会考、但前端必考的语法特性。我曾经见过一道题是这样的给定一个数组要求在不改变原数组的情况下筛选出所有大于 10 的偶数并排序。看似简单但如果你在代码里直接用 sort()就会改变原数组的引用这在笔试环境下可能不报错但如果题目用了深度比较来校验结果就会扣分。另一个常见考点就是“手写实现”。比如让你实现一个防抖函数、一个节流函数、一个深拷贝、一个数组去重。这些题目在业务开发中直接使用 Lodash 就能解决但笔试考的就是你能否脱离工具库理解它们的实现原理。包括类型判断、for-in 和 for-of 的区别、symbol 的存在是否会破坏 for-in这些细节点都需要在平时写代码时积累。还要提一个点现在的笔试系统大多支持多语言但前端岗位的候选人普遍会选 JavaScript。这就意味着你提交的每一份代码不仅要让结果正确还要让阅卷人或者自动评分系统看到你写出的是“可维护的代码”而不是“在 LeetCode 上抄来的答案”。变量命名、注释、函数拆分这些工程化习惯在笔试里同样是加分项。2.2 高频考点概览从字符串到算法的四个层次结合搜狗以及同级别公司的历年真题我把前端编程题的考点划分为四个层次你可以按这个层次去做自测看看自己目前处在哪个阶段第一个层次是语言基础层典型的题目就是字符串反转、数字格式化、数组扁平化、数组去重、对象深拷贝。这些题目不涉及复杂的算法思想但需要你对 JavaScript 的 API 非常熟悉。很多人在这个层次就会踩坑比如用 Array.from 处理类数组和 Set 的去重陷阱或者用 arr.flat(Infinity) 直接搞定扁平化却不清楚 flat 在低版本浏览器里的兼容性问题。第二个层次是数据处理层典型的题目包括数组去重并排序、按对象属性分组、多字段排序、树形结构的查找与遍历。这类题目已经贴近业务场景比如把后端返回的扁平列表转换成一棵菜单树或者把一组订单按日期和状态做聚合。这类题考的是你对数据结构的理解以及在 JavaScript 中高效操作对象和数组的能力。第三个层次是设计实现层典型的题目是“手写发布订阅 EventEmitter”、“实现一个支持并发数限制的调度器”、“封装一个带缓存的请求函数”等。这类题没有标准答案重点考察你的程序设计能力和对异步流程的控制能力通常也是面试官后续追问的素材。第四个层次才是真正的算法层典型题目是最大子序和、最长不重复子串、爬楼梯问题、括号生成等经典题目。前端岗位对算法深度的要求通常低于后端但简单到中等的 DP 和递归题还是可能会出现在压轴位置尤其是一些规模较大的公司喜欢用这种题来测试候选人的逻辑天花板。2.3 出题风格的隐性要求边界条件才是真正的分界线很多同学刷完题之后对答案发现自己的代码“逻辑上没问题”但提交后就是不能全过。这时候 90% 的问题是出在边界条件上。前端笔试的判题系统不会只看核心逻辑对不对它会用一整套测试用例包含空数组、单元素数组、负数、极大值、极小值、重复值、特殊字符、嵌套结构等任何一个边界没有覆盖到就是 fail。举一个真实的例子题目要求把给定的字符串按单词反转意思是将 “hello world” 变成 “world hello”。有人用 split(‘ ’) 分割然后 reverse 再 join表面上通过了示例用例但一旦输入是 “hello world”两个空格结果就会出错因为 split 会产生空字符串。正确的做法是用 split(/\s/) 或者先 trim 再 split。这种差异在示例用例中根本看不出来只有在提交时才会暴露。所以我的建议是在写代码的时候不要先追求“通过示例”而是先在脑子里过一遍“哪些输入会让我的代码崩溃”。输入为空数组时输出应该是什么数组长度为 1 时循环能不能跑字符串包含空格、换行、制表符时要不要做归一化排序时元素相等怎么办这些边界条件的思考过程就是你和只刷了示例用例的候选人的差距所在。3. 高频考点的核心解法与踩坑复盘3.1 字符串类模板解析与拼接背后的正则功底字符串处理是前端笔试的常客很多人觉得简单但真到做题时往往在正则上栽跟头。比如常见的“将下划线命名转换为驼峰命名”核心是匹配下划线加小写字母的组合然后替换成大写。function toCamelCase(str) { return str.replace(/_([a-z])/g, (_, char) char.toUpperCase()); }这里的关键点在于正则分组和回调函数的参数位置不熟悉 replace 第二个参数是函数的话很容易卡住。还有一道考得很多的题是“格式化数字”比如把 1234567 转成 1,234,567。很多人会写一个循环去处理但实际上用正则一行就能解决function formatNumber(num) { return num.toString().replace(/\B(?(\d{3})(?!\d))/g, ,); }这道题的真实难点在于理解正向预查和负向预查的含义。我在笔试现场见过很多人能背出这个正则但一旦题目改成保留两位小数的格式化就不知道怎么变通了。所以我不建议死记硬背正则而是要理解“位置锚定”的思想。再比如字符串去重、统计字符出现次数、判断回文这些题目虽然考察的是字符串 API但同样会牵扯到一些数据结构基础。统计字符出现次数的常用技巧是使用 Map 或对象记录频率这里我要提醒一句用对象存储时如果字符是proto或者 toString 这类特殊键名就可能会出错用 Map 更安全。这种细节平时写业务代码可能无所谓但在笔试的隐藏测试用例里它就是致命的。3.2 数组与对象去重、排序、深拷贝的隐藏陷阱数组去重是前端笔试出现频率最高的题目没有之一。从最开始的 Set 一行流到用 filter 加 indexOf再到用 reduce 构建哈希表这道题考察的是你对多种解法的理解深度。// 基础版利用 Set 去重 const unique (arr) [...new Set(arr)]; // 兼容对象元素的版本 function uniqueByKey(arr, key) { const map new Map(); arr.forEach(item { if (!map.has(item[key])) { map.set(item[key], item); } }); return [...map.values()]; }Set 去重很简单但它只能区分基本类型的相等性NaN 和 NaN 会被视为相同而对象永远不相等。如果题目要求按对象的某个属性去重就必须引入哈希表。这是很多人的知识盲区以为会 Set 就够了但实际上笔试题目经常会加一个条件“按 id 字段去重”。数组排序是另一个重灾区。JavaScript 的 sort() 默认按字符串字典序排序而不是数字大小排序这是新手最常见的错误。即使知道要传比较函数也容易在升序降序上传反。还有一个更深层次的坑sort() 的稳定性在不同引擎中表现不同V8 在元素数量小于 10 时用插入排序大于 10 时用快速排序的变体虽然现在已经基本稳定但如果你在排序时依赖了原数组的顺序最好还是先明确比较函数。深拷贝这个考点就更经典了。用 JSON.parse(JSON.stringify(obj)) 能解决大部分场景但它会丢失 undefined、函数、Symbol、循环引用也会把 Date 转成字符串。笔试中如果题目明说“对象中包含函数请实现完整深拷贝”你就要写递归版本了。递归的难点在于处理循环引用标准做法是用 WeakMap 记录已经拷贝过的对象遇到相同的引用直接返回拷贝结果避免死循环。我建议每个准备笔试的人都亲手写一遍这个 deepClone不是为了背代码而是为了理解递归和引用传递的本质。3.3 函数式编程防抖、节流、柯里化是业务和笔试的重叠区前端笔试很喜欢考的函数式编程题目其实就是工作中每天都在用的那几件套。防抖和节流的区别几乎每个面试官都会问笔试则会让你直接写出实现。核心在于对 this 和 event loop 的理解函数内部的定时器清理方式直接决定了行为是否正确。function debounce(fn, delay 500) { let timer null; return function(...args) { if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); timer null; }, delay); }; }这里有一个很多资料不会提醒你的细节回调函数里要使用 fn.apply(this, args)把当前上下文的 this 传递进去。因为返回的包装函数是被调用者直接触发的它内部的 this 可能指向事件源比如按钮、输入框如果你写作 fn(...args)this 就会丢失。这种细节在笔试里可能不会被用例捕捉到但面试官看代码时会注意到。节流和防抖的区别在于防抖是事件停止触发后才执行节流是每隔一定时间必须执行一次。实现节流有两种思路一种是用时间戳对比一种是用定时器。时间戳版本适合“需要立即执行第一次”的场景定时器版本适合“最后一次执行要延迟”的场景。笔试题目经常直接说“实现一个节流函数”不会告诉你边界怎么处理所以你需要在代码注释里写清楚你选择哪种策略这样阅卷人才能理解你的设计意图。柯里化题目出现的频率也不低比如实现 sum(1)(2)(3) 等于 6或者实现一个通用的 curry 函数。通用 curry 的递归思路是关键当传入的参数数量达到原函数需要的长度时就执行原函数否则返回一个新的函数继续收集参数。这里有一个隐含的考点就是如何判断参数数量已经攒够了一般通过 fn.length 获取函数定义的参数个数。如果不支持 fn.length就只能用 arguments 和闭包手动统计了。3.4 经典算法查找与动态规划入门前端也需要会前端笔试的算法题深度上不会太夸张但至少会碰到一两道涉及二分查找、滑动窗口、动态规划入门级别的题目。比如“给定一个有序数组找到目标值的插入位置”这是标准的二分查找变形要求你写出边界正确、不会死循环的实现。很多人在这类题上出事不是不会二分而是 while 循环的左右边界写错要么死循环要么漏掉边缘元素。另一个高频题目是“最长不重复子串长度”用滑动窗口可以做到 O(n)。这个算法思想本身不难但第一次接触的人很难想到用 Map 来记录字符最后一次出现的位置从而快速收缩左边界。我的建议是在笔试前把 LeetCode 的 hot 100 中简单和中等难度的字符串、数组类题目过一遍不需要刷完但要把每个题型的最优解思路理清。动态规划在前端笔试中出现频率低一些但一旦出现通常是爬楼梯、最小路径和这类基础题型。递归加记忆化搜索是最容易写出来的版本先不管状态转移方程是否最优至少保证能跑通。如果你能写出自底向上的递推版本那在代码层面就已经超过大部分候选人了。很多人卡在 DP 上其实是卡在对“状态定义”的理解上比如爬楼梯问题 dp[i] 代表什么、为什么 dp[i] dp[i-1] dp[i-2]这些问题只要想通了后面遇到类似问题就能举一反三。4. 笔试环境与答题策略这些细节决定了你能拿多少分4.1 不同在线评测系统的差异代码风格要求完全不一样很多人忽略一个关键点搜狗那场笔试用的在线评测系统和你在 LeetCode、牛客网上刷题的系统输入输出模式可能完全不同。牛客网很多前端题是“核心代码模式”你只需要完成一个函数输入参数已经传好了而有些公司用的是“ACM 模式”你要自己处理 stdin/stdout也就是从输入流读取数据再把结果打印到输出流。这两种模式对代码结构的要求差异非常大。如果你只练过核心代码模式突然遇到 ACM 模式连怎么读取多行输入都不知道那就是灾难。所以在准备阶段一定要看历史笔试题型说明确认是哪种模式。如果是 ACM 模式就要提前练习 readline 的使用const readline require(readline); const rl readline.createInterface({ input: process.stdin, output: process.stdout }); rl.on(line, (line) { // 处理每一行输入 });还有一个细节是输入数据的解析。ACM 模式的第一行通常是“测试用例组数”或“数组长度”需要用 split 把字符串拆成数组再逐个转成数字。很多人忽略了数字和字符串的区别直接用字符串去做比较或运算结果得到错误的输出。这个问题在平时开发中不常见因为 TypeScript 和严格模式会提醒你但在笔试的纯 JavaScript 环境下就非常容易翻车。4.2 边界情况和输入处理是所有隐藏用例的重点隐藏测试用例和示例用例的区别就在于边界。我整理了一个边界检查清单每次写完代码后过一遍能帮你减少大量失误输入为空或长度为 0 时代码会不会异常数组只有一个元素时循环和索引是否正确输入包含负数或 0 时逻辑是否依然成立字符串包含空格、大小写混合、空字符串时是否做了处理排序数组时两个元素相等是否会影响后续逻辑对象或数组嵌套层级很深时递归是否会导致栈溢出是否修改了题目不允许修改的原数组或原对象我曾经在一次模拟笔试中遇到一道题要求把给定的二进制字符串反转后输出对应的十进制数。题目示例给的是 “1010”很多人实现时直接用的是 Number.parseInt但遇到前导零的字符串 “001010” 时反转后的结果就会出错因为数字字符串的前导零在 parseInt 时会被忽略。这就是典型的边界测试用例除非你看到过类似案例否则很难提前想到。我个人的做法是在写完代码之后自己构造至少 5 组额外用例包含一个空输入、一个极端输入、一个重复值较多的输入、一个顺序逆序的输入、一个超大数值的输入。跑通这 5 组用例再提交。这个方法能过滤掉绝大多数低级错误。4.3 时间分配与做题顺序按四三三原则来一场前端在线笔试的时间通常在 90 到 120 分钟题目数量在 4 到 6 道之间其中包含选择题和编程题。如果严格按照分值来分配我一般建议按“四三三”原则来规划前 40% 的时间用来快速做完全部选择题和简单编程题保证基础分拿到手中间 30% 的时间集中攻克两道中等难度的编程题最后 30% 的时间留给最难的那道题同时用最后 10 分钟检查前面代码的边界和命名。这套时间策略的核心逻辑是不要在最难的题上死磕超过 20 分钟。如果 20 分钟没有思路先跳过完成后面能拿到的分再说。很多时候心理压力降低之后回头再看难题反而会灵光一现。我见过太多人卡在第一题上写了 40 分钟结果后面四道题全空着总分反而很低。关于做题顺序建议先看一遍所有编程题判断每道题的难度从自己最有把握的开始做。先做简单的题能建立信心也能在心理上形成“我已经拿到保底分”的安全感。另外遇到题干描述特别长的题目不要慌一般信息量越大解法越基础它只是把业务场景包装得很复杂拆解之后往往是字符串拼接、数组遍历、条件判断的组合。5. 手写代码清单考前照着撸一遍5.1 高频手写题及其参考实现我给准备前端笔试的朋友整理了一个手写代码清单这些题目在搜狗和其他一线公司的笔试中反复出现。我提供一个精简版的参考实现但不建议直接背代码而是要理解每一行的作用和边界。第一题手写 instanceoffunction myInstanceof(left, right) { let proto Object.getPrototypeOf(left); while (proto) { if (proto right.prototype) return true; proto Object.getPrototypeOf(proto); } return false; }第二题实现 Promise.allfunction promiseAll(promises) { return new Promise((resolve, reject) { const results []; let count 0; promises.forEach((p, index) { Promise.resolve(p).then(value { results[index] value; count; if (count promises.length) resolve(results); }, reject); }); }); }这两个实现都非常经典。Promise.all 的实现有两个细节要注意第一个是结果数组的下标必须与传入的 promises 顺序一致不能用 push第二个是当 promises 为空数组时应该直接 resolve([])但上面的代码在 count 初始化为 0 的情况下不会走到 resolve所以需要额外处理一下。这个边界即使在一些网上给的“标准答案”里也没有覆盖到但在笔试中只要测出来就是零分和满分的区别。第三题是发布订阅模式EventEmitter。它的实现本质上是维护一个事件名到回调函数数组的映射on 方法负责注册emit 方法负责触发off 负责移除。这里面比较容易忽略的是 on 注册时传入同一个 fn 两次需要去重以及 once 包装函数的实现。once 的实现逻辑是包装一个函数执行完原函数后自动调用 off但不小心就会漏掉 this 绑定和参数传递这些细节需要你自己动手写一遍才能发现问题。除此之外手写 Object.create、手写 call/apply/bind、手写数组的 map/filter/reduce也都是高频考点。这些内容在红宝书和 MDN 上都有详细说明但“看过”和“写得出”之间差着十万八千里我建议每天挑三个出来手写一遍写错的地方重点关注一周后你会发现自己对 JavaScript 的理解有了质的变化。5.2 代码命名与结构第一个印象分藏在这里笔试的自动评分系统虽然不检查变量命名但面试官会看。如果代码里的变量全是 a、b、c函数名是 fn那么即使通过了所有用例也会在后续的技术面试中留下不好的印象。反过来说一份代码结构清晰、变量名表意明确、关键步骤有注释的答卷很容易让面试官产生“这个人代码习惯不错”的判断。我建议在笔试中保持和工程代码一致的风格使用语义化命名比如 inputArr、targetValue、resultMap函数名用动词短语比如 findMax、mergeData、formatList超过三行的逻辑块尽量抽成独立函数复杂算法在开头用一行注释说明思路。这套做法不会消耗太多额外时间但对最终评价有正向帮助。还有一点是模块化思维。如果题目拆成了多步处理比如先解析输入、再过滤数据、最后格式化输出你可以把每一步写成不同的函数然后在主函数里调用。这样不仅代码更清晰调试时也更容易定位问题。我自己在面试候选人的时候第一眼看的不是答案对不对而是看代码能不能让我在五分钟内读懂他的思路这点很多刚毕业的同学都没有意识到。5.3 错题复盘怎么做才有效刷题不复盘等于白刷。复盘不是看一遍标准答案再把自己的代码改对就结束了而是要把这道题背后的知识点拆解出来记录到错题本里。我常用的复盘模板包含四个部分这道题考察的核心知识点是什么我的代码在哪里出了问题是语法、边界、还是算法思路标准解法和我的解法差距在哪里为什么更优这类题的通用套路是什么能否迁移到别的场景比如你做错了一道“数组按对象属性分组”的题目复盘时你会意识到核心考点是 reduce 搭配 Map 的用法以及 Optional Chaining 的兼容性问题。你会顺便发现这种分组逻辑在业务中可以做订单按月份归类、可以做图表数据按类别聚合于是你总结出“遇到分组题先想 Map 构建哈希表”的套路。这样的复盘才算把一道题吃透了。我遇到过很多同学一道题刷了五遍第六次遇到稍微变形的版本还是不会原因就是没搞清楚通用套路只是在背题。为了避免这种低效努力建议大家做完每一道题后都尝试换一个说法描述这道题。比如“数组去重”可以变形为“根据某个字段去重”、“让重复项保存最后出现的那条”、“在去重的同时统计每个元素出现的次数”。把这些变体想一遍你对这道题的理解就完全不同了。6. 写在最后的一些实操心得如果这套题有足够的样本你会发现它真正想筛掉的不是“算法不好的人”而是“遇到问题没有分析思路的人”。很多笔试拿高分的同学并不是因为刷题量多到惊人而是他们在看到任何一道没见过的题目时都能快速进入“拆解问题”的模式。比如看到“字符串解码”的题先判断能不能用栈看到“岛屿数量”的题第一反应是 DFS看到“数组中的最大和”的题立刻想到 Kadane 算法。这种条件反射就是靠大量刷题加结构复盘训练出来的。再说一个很多攻略不会提的小技巧充分利用本地编辑器。笔试通常允许打开本地 IDE 或在线编辑器不要在答题框里直接裸写代码。先在本地写好、跑通测试用例再粘贴过去能极大减少低级语法错误的概率。本地编辑器还能让你快速验证边界情况比在答题框里反复提交高效得多。不过要注意时间不要因为开着本地环境就过度调试笔试的计时不会因为你本地写代码而暂停。最后关于编程语言选择的问题。虽然前端岗位默认 JavaScript但有些同学的 Python 或 Java 基础更扎实。这里我想说一个实际观察如果笔试系统支持多种语言前端候选人最好还是用 JavaScript 交卷因为题目本身就是围绕前端场景设计的用 JavaScript 实现会显得更自然也更容易在后续面试中展开讨论。但你依然可以用 Python 或者任何语言去验证思路把算法逻辑想清楚了再翻译成 JavaScript这也是一个值得培养的能力。毕竟编程题考的是思路和基本功语言只是表达工具。
返回列表