ARTICLE DETAIL

资讯详情

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

搜狗前端笔试精讲:大数相加与手写Promise核心解析

搜狗前端笔试精讲:大数相加与手写Promise核心解析 搜狗2019秋招前端工程师编程题合集第一场我翻来覆去刷了很多遍。作为一个常年混迹在校招笔试题里的前端工程师这套题给我最直观的感受是它不像很多公司那样堆偏难怪题而是把“搜索引擎场景下的数据处理”和“前端语言特性”揉在一起考每一道题都能看出出题人真正想验证的能力点。无论你是准备投大厂校招还是工作两三年想补基础这套题都值得花时间过一遍尤其是里面的大数相加和手写Promise几乎就是前端笔试的“必修课”。不少同学面对编程题有个误区觉得前端笔试就考算法拼命刷LeetCode结果真上场时发现考的是手写一个防抖、解析一个URL参数反而慌了。这篇博文我就把这套合集里最有代表性的几类题目拿出来一道一道拆思路、写代码、讲边界最后再聊聊我在笔试现场踩过的坑和复盘方法。内容比较多但看下来你至少能少走一个月的弯路。1. 搜狗第一场前端笔试题到底在考什么1.1 校招前端笔试的本质用代码暴露你的思维短板很多人把编程题等同于“算法题”其实校招笔试的编程题考察的是三件事的叠加基础知识是否扎实、代码风格是否规范、在有限时间内能否把思路转成可运行的程序。搜狗这套题尤其明显它不会像ACM那样给一个纯数学背景的难题而是把题目包装在很贴近业务的语言里比如处理一段URL、解析一组数据、控制一段异步流程。这种出题方式对我的启发很大。前端日常开发中大量问题不是“这个算法不会”而是“代码写出来能不能cover所有边界”。一个URL参数解析看起来五分钟能写完但等到跑测试用例才发现“同样的key出现两次怎么办编码后的中文要不要decode这个参数没有值该怎么处理”笔试真正筛选的就是这种对完整性的把控能力。1.2 从题型分布反推出题人的需求搜狗本身是搜索引擎公司前端团队每天面对的是大量文本、搜索词、页面渲染、异步请求所以笔试题目倾向于三大方向字符串和数据处理、JS语言机制的手写实现、场景化的性能优化。这三个方向几乎覆盖了前端笔试的主流考察面刷完这套题相当于把前端基础能力的关键节点都摸了一遍。我把这套合集里出现频率较高、最具代表性的题目方向整理成了下面这个表格方便大家快速定位哪类题必须重点准备。题目方向典型考察点常见陷阱建议用时字符串与数字处理大数相加、字符串变换数值精度丢失、进位遗漏15-20分钟异步与状态管理手写Promise、事件循环输出微任务机制、then链返回25-30分钟场景化数据处理URL参数解析、数组去重变体编码解码、同名参数、引用类型10-15分钟性能优化手写防抖、节流、深拷贝this指向、定时器清理、循环引用15-20分钟1.3 这套题适合哪些人刷我的建议是只要目标是前端方向不管现在是大二还是已经工作都应该拿这套题来自测一遍。没开始刷题的同学可以先不设时间限制把每道题当成一个“小项目”来做先保证思路对再追求代码能跑。已经有一定刷题量的同学直接按笔试环境限时训练做完之后重点看边界测试和代码简洁度。这里有一个额外心得这道题虽然已经是2019年的但前端笔试的考察逻辑并没有发生太大变化。现在的大厂笔试依然在考手写Promise、实现防抖节流、处理URL参数只是场景换了一层皮。所以这套题完全不过时它代表的是前端岗位面试中最稳定的一类底层能力考核。2. 四类核心考题精讲从思路到实现2.1 字符串处理和精度问题大数相加大数相加是这套题里很有代表性的一道也是我强烈建议每个人都亲手写一遍的题目。它的业务背景非常直接JS里Number类型能安全表示的最大整数是2的53次方减1超过这个范围就会出现精度丢失。当后端返回一个超过安全范围内的ID或者金额时前端不能直接参与运算只能把数字当作字符串来处理。解题思路就是模拟小学竖式加法从两个字符串的末位开始逐位相加逢十进一。注意几个关键点两个字符串长度可能不一样某一位相加之后可能产生进位所有数字相加完后如果还有进位要在结果最前面补一位。下面是完整的参考实现。function addStrings(num1, num2) { let i num1.length - 1; let j num2.length - 1; let carry 0; let result ; while (i 0 || j 0 || carry 0) { const a i 0 ? Number(num1[i]) : 0; const b j 0 ? Number(num2[j]) : 0; const sum a b carry; carry Math.floor(sum / 10); result (sum % 10) result; i--; j--; } return result.replace(/^0/, ) || 0; }这里我特意在最后做了一次前导零清理因为题目如果输入0001和0002直接相加会得到0003很多实现没处理这种情况。如果真的在笔试中被隐藏用例卡住大概率就是卡在这里。时间复杂度是O(n)只需要一次遍历已经是最优解法。我在实际调试中踩过的坑是有同学会把Number(num1[i])换成parseInt(num1[i])这两种写法本身没问题但如果做字符到数字转换时忘记了字符可能不是数字就会埋下隐患。另外循环条件里carry 0这个判断非常关键不然两个数相加刚好得到整十数时最高位的进位会丢掉。2.2 异步和状态机手写一个迷你Promise手写Promise几乎是所有大厂前端笔试的必备题搜狗这套题也不例外。这道题表面上考的是API实现实际上考的是对Promise核心机制的理解状态只能从pending变为fulfilled或rejected且一旦改变就不可逆then方法可以注册回调并且必须返回一个新的Promise以支持链式调用异步的resolve/reject需要等当前同步代码执行完再触发回调。写一个能通过基础用例的迷你Promise核心是把状态管理和回调队列管理好。我给出的参考实现如下这个版本没有完全实现Promise/A的每个细节但作为笔试作答已经能覆盖大部分测试点。class MyPromise { constructor(executor) { this.state pending; this.value undefined; this.reason undefined; this.onFulfilledCallbacks []; this.onRejectedCallbacks []; const resolve (value) { if (this.state pending) { this.state fulfilled; this.value value; this.onFulfilledCallbacks.forEach(fn fn()); } }; const reject (reason) { if (this.state pending) { this.state rejected; this.reason reason; this.onRejectedCallbacks.forEach(fn fn()); } }; try { executor(resolve, reject); } catch (e) { reject(e); } } then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) { const handleFulfilled () { try { const result onFulfilled ? onFulfilled(this.value) : this.value; resolve(result); } catch (e) { reject(e); } }; const handleRejected () { try { if (onRejected) { resolve(onRejected(this.reason)); } else { reject(this.reason); } } catch (e) { reject(e); } }; if (this.state fulfilled) { setTimeout(handleFulfilled, 0); } else if (this.state rejected) { setTimeout(handleRejected, 0); } else { this.onFulfilledCallbacks.push(() setTimeout(handleFulfilled, 0)); this.onRejectedCallbacks.push(() setTimeout(handleRejected, 0)); } }); } catch(onRejected) { return this.then(null, onRejected); } }在笔试环境下如果能写出这个程度基本上可以拿到大部分分数。但有几个细节需要注意第一then里传入的回调如果返回的是一个Promise规范的实现是需要等待这个Promise完成后再继续传递这里简化成了直接resolve严格来说不算完整第二setTimeout模拟异步只是权宜之计真正规范中应该用微任务但在笔试中这么写不会扣太多分因为你已经展示了对异步时机的理解。我建议大家写完这个版本后自己跑三个测试用例一个普通链式调用、一个reject后走catch、一个executor里抛异常。这三个用例都能通过说明你对核心流程的理解是到位的。2.3 场景化API设计URL参数解析搜狗做搜索产品URL参数解析这种场景化题目几乎是量身定做。题目通常会要求把一个queryString解析成对象需要处理中文编码、同名参数、无值参数等情况。这道题的核心考点非常清晰decodeURIComponent是否记得用、同名参数是否会合并成数组、没有等号的参数怎么取值。参考实现如下function parseQuery(queryString) { const result {}; if (!queryString) return result; const str queryString.charAt(0) ? ? queryString.slice(1) : queryString; if (!str) return result; const pairs str.split(); for (const pair of pairs) { if (!pair) continue; const idx pair.indexOf(); const key idx 0 ? decodeURIComponent(pair.slice(0, idx)) : decodeURIComponent(pair); const value idx 0 ? decodeURIComponent(pair.slice(idx 1)) : ; if (Object.prototype.hasOwnProperty.call(result, key)) { if (Array.isArray(result[key])) { result[key].push(value); } else { result[key] [result[key], value]; } } else { result[key] value; } } return result; }有人可能会问直接用split()不是更简单吗确实split()在处理简单场景时更省事但一旦value本身包含等号比如?linkhttps://example.com?a1b2split会把链接切碎。用indexOf()找第一个等号就能正确处理这种场景。这是实际开发里非常常见的坑笔试中能想到这里说明你写代码不是照搬模板。另一个容易被忽略的细节是解析出的key可能以数组形式存在但只在第二次遇到同名参数时才转为数组。很多同学一上来就固定result[key] []导致只有一个参数时变成了数组这和实际需求不符。所以我在代码里保留了hasOwnProperty的判断这个写法在业务开发中也一样适用。2.4 高频事件性能优化防抖和节流的实现与选型前端笔试考防抖节流搜狗这套题里的考法通常是给一个场景比如输入框实时搜索、页面滚动加载更多让你实现对应的性能优化函数并解释为什么用防抖而不是节流。这两个函数看似简单但能写对的人比例并不高原因在于很多人没有真正理解它们的视角差异。防抖的核心是“延迟执行重新触发则重新计时”适合输入框搜索这种“停止输入后才真正发起请求”的场景节流的核心是“固定时间间隔内最多执行一次”适合滚动加载这种不能太频繁触发但也不能一直不执行的场景。参考代码如下function debounce(fn, delay) { let timer null; return function (...args) { if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); }, delay); }; } function throttle(fn, interval) { let last 0; return function (...args) { const now Date.now(); if (now - last interval) { last now; fn.apply(this, args); } }; }这里有一个很容易被忽略的点setTimeout回调里的fn.apply(this, args)为什么要保留this因为防抖节流通常用在对象方法上如果直接调用fn(args)this会变成undefined这在严格模式下直接报错。实际开发中我见过太多人写防抖时掉进this陷阱所以笔试时一定要体现出来。节流还可以用定时器实现一个带尾调用的版本不过笔试中给出时间戳版本已经足够因为它的核心逻辑很干净。如果你时间充裕可以再补一个带trailing参数的进阶版本这样能明显拉开和普通人的差距。3. 实操过程与边界控制我就这么一步步调通的3.1 本地调试环境的搭建思路笔试不同于日常开发没有IDE提示、没有调试器很多代码写出来不跑一遍根本不知道哪里有问题。所以我强烈建议平时刷题时就在本地搭一个最小调试环境。不需要装任何复杂框架只需要Node环境写一个简单的test.js把函数和测试用例放在同一个文件里用node test.js直接跑。比如写大数相加时我会在代码下方手动加几组测试console.log(addStrings(123, 456)); // 579 console.log(addStrings(999, 1)); // 1000 console.log(addStrings(0, 0)); // 0 console.log(addStrings(001, 002)); // 3 console.log(addStrings(50, 50)); // 100调试环境搭建的核心价值是让错误快速暴露逼着你把边界补全。我第一次写大数相加时以为写完就完了结果测试999 1直接输出990就是进位没处理对。这种从失败到修正的过程是刷题最有价值的部分。3.2 边界用例清单笔试前必测的10个输入我在刷题复盘时给自己总结了一份“必测清单”每次写完一道题都会按清单过一遍。现在分享出来这份清单基本可以覆盖大多数字符串处理类编程题的隐藏用例。输入为空字符串或null的情况。输入长度差很大的情况比如一个一位数、一个一万位数。结果产生最高位进位的情况比如999加1。输入包含大量前导零的情况。数字字符和特殊字符混合的情况。URL参数中value包含号或号的情况。同名参数连续出现多次的情况。参数没有号只有key的情况。参数值已经是编码状态的情况。参数顺序需要保持原始顺序的情况。每一条都对应一种实际场景。比如URL参数中的?a1a2a3如果解析结果不是数组后续业务比如多选筛选就全炸了。这些细节在笔试中往往就是区分通过与否的关键。3.3 时间复杂度和代码规范上的隐性加分项编程题不是“能跑就行”尤其是大厂笔试代码质量直接反映你的工程习惯。我习惯在写完代码后做三件事第一检查循环次数是否可以优化第二检查是否有不必要的变量和分支第三给核心函数加上清晰的命名和适当注释。比如大数相加如果我看到有人直接写成Array.from(num1).reverse()再循环虽然也能过但多了一次O(n)的反转和一次O(n)的数组创建。直接用下标从末位往前遍历代码更短内存占用更少。这种细节在面试官眼里就是加分项。另外一个常见问题是尽量别用递归处理深度未知的数据。比如深拷贝用递归实现看起来简洁但如果对象层级很深直接栈溢出。笔试时可以先用递归拿分但复盘时一定要补一版用栈模拟递归的迭代实现。4. 常见丢分点与排查技巧实录4.1 笔试现场最容易翻车的三个习惯第一个习惯是拿到题目就写代码不先理清边界。我见过很多同学在URL参数解析这道题上翻车就是因为没想清楚“同名参数要合并成数组”这个需求直接按普通键值对处理结果隐藏用例一跑就挂。正确的做法是先用两分钟在草稿纸上写出输入输出的映射关系把所有分支列出来再动手。第二个习惯是过度依赖内置API。数组去重题你用new Set()当然可以但如果题目明确要求不借助Set或者数据量极大且要求保持顺序直接Array.from(new Set(arr))就不合适。刷题时多想想内置API的原理比背一百个API用法有用得多。第三个习惯是只写函数不写验证代码。笔试平台的判题系统确实会自动跑测试用例但你在本地练习时不写测试等于闭着眼睛开车。手动跑几个用例能在五分钟内找到逻辑漏洞这个时间花得非常值。4.2 遇到不会的题怎么保住基本盘笔试场上肯定会遇到没思路的题这时候最重要的不是蒙一个答案而是展示你现有的思考过程。很多在线笔试系统支持打印日志你可以先写一个朴素版本、分析它的时间复杂度再把优化思路写在注释里。面试官看到的是“这个人知道暴力解法也知道瓶颈在哪”这比空着不写要好太多。以手写Promise这道题为例如果一上来就写完整版很多人会卡在then链返回新Promise这个逻辑上。我的建议是分步走第一版只写状态机和resolve/reject第二版加then回调注册第三版再处理链式调用和异常捕获。每写一个版本都跑一下测试确保当前步骤没有引入新的bug。4.3 复盘方法从一道题反推出完整知识体系刷题最有价值的环节不是“做出来”而是“复盘”。我每次做完一道题都会问自己三个问题这道题考的是哪个底层知识点我为什么一开始没想到最优解如果把题目变一个场景我能不能套用同样的思路比如大数相加复盘时我会顺着它梳理JS精度问题的完整体系Number的存储结构、最大安全整数、浮点数运算误差、BigInt的出现时间和使用限制。这样一道题就不再是一道题而是一个知识簇。URL参数解析也可以延伸出URLSearchParams的兼容性、encodeURIComponent和encodeURI的区别、query与hash之间的边界问题。我当时复盘这套题时给自己列了一张知识补全表每个题目对应了三到五个延伸知识点然后利用一周时间把表格里不熟的部分逐个攻破。学完之后再回头看这些题会发现它们比想象中简单因为真正难的是知识体系的完整程度而不是单道题的解法。最后再说一个实用技巧刷题时准备一个文件专门记录自己犯过的低级错误比如“忘记判断空输入”“用了严格相等却没考虑类型转换”“在两个长度不同的字符串上直接把下标对齐”。每次笔试前翻一遍这个文件比临时看十篇面经都管用。这套搜狗编程题我已经刷过好几遍每次都能从里面翻出新的收获希望这篇拆解也能帮你把每一道题吃透。
返回列表