贝壳这套秋招笔试题我是在第二批做的。整体感受是题量不小覆盖面偏基础但坑点多选择题考得细编程题不考偏题怪题但非常考验编码熟练度和边界意识。如果你正准备贝壳或其他大厂的前端秋招这篇复盘值得认真看完。说说这份博文适合谁马上要参加前端笔试的应届生想看真实题型和难度的人以及在备战过程中想系统补基础的同学。内容全部基于2024年秋招贝壳找房前端工程师第二批笔试的实际情况我按题型、考点、编程题思路、踩坑点四个维度拆开讲尽量还原考试现场。1. 笔试整体信息与时间分配策略1.1 贝壳笔试的流程与节奏贝壳找房的秋招流程一般是网申、测评/笔试、面试一般三轮技术一轮HR第二批笔试通常安排在9月中下旬。笔试是在牛客网这类在线平台进行的全程开启摄像头监控手机扫码进入第二机位浏览器会锁定不能切屏切屏次数过多会触发警告甚至交卷。从实际体验来看贝壳第二批笔试的总时长是90分钟题量在40道左右主要由两部分构成客观题单选多选涵盖HTML/CSS/JavaScript/网络/浏览器/数据结构和多道编程题。第二部分编程题一般是2~4道具体数量每批会稍有浮动我这次遇到的是2道题其中一道是算法题、一道是前端场景题但看牛客上其他批次的反馈也有4道的所以准备时不能只押一种组合。这套题给我的整体感觉是选择题考得细很多是“你觉得你会但一选就错”的题需要基础非常扎实编程题不是ACM风格那种绕来绕去的难题更像是平时开发中会遇到的场景抽象但如果你只会写业务代码、不熟悉基本数据结构和常见手写题会卡在边界处理上。1.2 时间分配才是第一道大题我先把时间分配建议放在前面因为这才是大多数人最容易失误的地方。90分钟客观题和编程题怎么分我的做法是客观题控制在45分钟左右留下至少40分钟给编程题。因为贝壳的客观题虽然量大但每道题思考时间不宜超过1分半。遇到卡壳的题先标记跳过最后有时间再回头千万别为一道选择题耗掉5分钟编程题的分值权重远高于选择题。其次编程题要先看一遍全部题目不要拿到第一题就埋头写。有时候第二题更简单先做容易拿分的题能稳住心态。我这次就是先扫了一遍编程题发现第二题是数组处理类比第一题简单果断反着做省了不少时间。另外贝壳的笔试平台支持本地IDE写代码再粘贴提交也支持网页在线编辑。建议提前用牛客网的在线编程环境练一练熟悉它的输入输出模板。特别是JavaScript模式要自己写readline处理输入很多人挂在输入输出上而不是算法本身。2. 客观题考点复盘与重难点解析2.1 考点分布与高频陷阱贝壳这批选择题的考点非常集中考完我做了统计基本上可以分成下面几类考点模块出现频率考察深度JavaScript基础this、闭包、原型链、作用域非常高细节抠得很深事件循环与异步宏任务/微任务/顺序输出高要求能写出输出顺序HTML/CSS语义化、Flex/Grid、定位、BFC中高偏布局细节浏览器与网络缓存、渲染流程、HTTP状态码中概念辨析前端工程化webpack、模块化、Vite中低基础概念不算深数据结构和算法基础复杂度、常见数据结构中低简单计算和判断可以看到核心还是JavaScript。这批题目里几乎没有偏难怪的“面试八股”但都是那种“见过但没认真想透”的知识点。下面我把印象深刻的几类题挑出来逐题说。2.2 高频考点this指向与作用域贝壳几乎每年都考this指向第二批也不例外。这类题要是没吃透基本靠猜。我遇到的是一道经典题var name window; var obj { name: obj, fn: function() { console.log(this.name); } }; var f obj.fn; f();答案是输出window。原因很简单f()是普通函数调用this指向全局对象浏览器里是window。但如果你以为贝壳就考这么简单那就错了。后面还跟着一道变体var name window; var obj { name: obj, fn: function() { return function() { console.log(this.name); }; } }; obj.fn()();依然是window。因为obj.fn()返回的匿名函数在执行时已经没有任何对象前缀属于独立调用。这个考点本质是this不是定义时确定的而是调用时确定的。只要记住“谁调用就指向谁独立调用指向window/undefined”这两条规则这类题就能稳稳拿分。2.3 高频考点事件循环与微任务/宏任务贝壳笔试的“输出顺序题”每次都有一道。这次考的是综合了Promise、setTimeout和async/await的经典组合。我把它整理成下面这个版本跟原题考查点基本一致async function async1() { console.log(async1 start); await async2(); console.log(async1 end); } async function async2() { console.log(async2); } console.log(script start); setTimeout(function() { console.log(setTimeout); }, 0); async1(); new Promise(function(resolve) { console.log(promise1); resolve(); }).then(function() { console.log(promise2); }); console.log(script end);正确输出顺序是script start、async1 start、async2、promise1、script end、async1 end、promise2、setTimeout。这里的核心坑有两个。第一await右侧的async2()是同步执行的所以会立即打印async2await后面那行相当于Promise.resolve().then()被放入微任务队列。第二new Promise里的构造器是同步执行的所以promise1在这里打印then里的回调才是微任务。执行顺序上微任务队列先清空再执行宏任务setTimeout。这道题我在准备阶段练过类似的所以没花太多时间但我知道有不少同学在输出顺序上栽了把async2放到了微任务阶段。2.4 高频考点原型链与继承贝壳考原型链不是让你默写而是给一段代码问结果。我记得有一道题考的是构造函数和实例的关系判断function Parent() {} Parent.prototype.say function() {}; const child new Parent();问哪个表达式为false。选项包括child instanceof Parent、child.constructor Parent、child.hasOwnProperty(say)、Parent.prototype.isPrototypeOf(child)等。答案是child.hasOwnProperty(say)为false因为say是原型上的属性不是实例自身的属性。这类题目其实考的是对原型链本质的理解实例通过__proto__访问构造函数的prototype所以instancof和isPrototypeOf都会返回true但hasOwnProperty只检查自身属性。说句实话这个知识点如果只是背概念看到实际代码很容易慌。建议大家复习时直接在控制台打印child、Child.prototype把结构看一遍比背十遍概念都管用。2.5 高频考点CSS布局与BFCCSS这批题里Flex布局几乎是必考。贝壳对Flex的考查侧重于属性效果比如flex-shrink的计算。我记得有道题问一个Flex容器宽度400px里面三个子元素flex-basis分别是200px、100px、100pxflex-shrink分别为1、2、3问三个元素收缩后最终宽度是多少。计算逻辑是总溢出量 (200100100) - 400 0实际上没有溢出所以每个元素都保持各自basis即200、100、100。如果容器宽度改成300px溢出量100px按权重分配三个元素的加权basis总和 200×1 100×2 100×3 700第一个元素收缩量 100 × (200×1/700) ≈ 28.57第二个 100 × (200/700) ≈ 28.57第三个 100 × (300/700) ≈ 42.86。所以最终宽度是171.43、71.43、57.14。这种题不能只记结论得理解flex-shrink的权重公式。BFC的题主要考触发条件float不为none、overflow不为visible、position为absolute或fixed、display为inline-block/table-cell/flex等。常见应用有清除浮动、防止margin塌陷、自适应两栏布局。贝壳考了一道“如何创建BFC”的多选选项里有display: flex和overflow: hidden答案就是这两个因为float和position也要选具体看选项组合总之这些触发条件都要记住。2.6 其他值得留意的考点另外还有几个选择题知识点虽然不是特别难但值得记录HTTP缓存Cache-Control的max-age、no-cache、no-store区别。注意no-cache不是不缓存而是每次使用前需要向服务器校验。浏览器渲染script标签的defer和async区别DOMContentLoaded触发时机。defer保证执行顺序且在DOM解析完后执行async是下载完立即执行可能阻塞解析。webpack的tree-shaking依赖ES Module静态结构所以commonjs模块无法tree-shaking。快排的时间复杂度平均O(n log n)、最坏O(n²)这个最坏情况很多人容易漏。3. 编程题实战思路与完整实现3.1 编程题总体感受贝壳第二批的编程题题目风格偏向“前端场景 基础算法”结合。不像字节那种上来就hard题贝壳更多是medium偏下但如果你平时只写页面、没写过算法题会觉得很吃力。编程题可以自己选择语言我用的是JavaScript所以下面的写法都以JS为主。需要特别注意的是牛客网上的JavaScript模式要自己处理输入输出不像LeetCode那样直接写函数。编程题的模板长这样const readline require(readline); const rl readline.createInterface({ input: process.stdin, output: process.stdout }); const lines []; rl.on(line, (line) { lines.push(line); if (lines.length n) { // 开始处理 } });千万别小看这部分很多人算法写对了却栽在readline读取上。我建议提前把模板背下来考试时直接改逻辑不值得在形式上浪费时间。3.2 编程题一手写Promise重试这道题大概是这样的场景实现一个retry函数传入一个返回Promise的函数fn、重试次数times和延迟时间delay要求失败后等待delay毫秒再重试最多重试times次全部失败则抛出最后一次错误。这类题考查的是对Promise链式调用和递归的掌握。直接看实现function retry(fn, times, delay) { return new Promise((resolve, reject) { function attempt(remaining) { fn() .then(resolve) .catch((err) { if (remaining 1) { reject(err); } else { setTimeout(() { attempt(remaining - 1); }, delay); } }); } attempt(times); }); }这个实现思路是用递归代替循环因为setTimeout是异步的用循环写没法等待延迟结束再继续。每次attempt都尝试调用fn成功就resolve失败则判断剩余次数还有剩余就等delay毫秒后再试否则reject最后一次的错误。如果你想在工程中限制总时长也可以结合Promise.race加一个超时控制。但笔试时能把这版递归写对、把边界情况说清楚就足以通过。这里有一个细节递归的attempt函数一定要把remaining - 1传下去不要在原值上修改避免出错。另外重试时重新调用attempt会创建一个新的Promise链之前的错误已经被catch捕获并处理不会出现unhandledrejection。3.3 编程题二LRU缓存机制第二题是经典的LRU缓存题目描述大概设计一个LRUCache类支持get和put操作容量满时淘汰最久未使用的数据要求在O(1)时间内完成。这个题在LeetCode上是146题贝壳把它原封不动搬过来了。JavaScript实现LRU最优雅的写法是用Map因为Map的插入顺序和读取顺序可以被我们利用。核心逻辑每次get时先判断是否存在存在则删除再重新set这样该key就被移动到了Map的末尾表示最近使用过put时先判断是否存在存在就先删掉然后判断size是否超过容量超过就删除Map的第一个key最久未使用的。class LRUCache { constructor(capacity) { this.capacity capacity; this.cache new Map(); } get(key) { if (!this.cache.has(key)) { return -1; } const value this.cache.get(key); this.cache.delete(key); this.cache.set(key, value); return value; } put(key, value) { if (this.cache.has(key)) { this.cache.delete(key); } this.cache.set(key, value); if (this.cache.size this.capacity) { const oldestKey this.cache.keys().next().value; this.cache.delete(oldestKey); } } }这个解法最关键的是利用了Map的迭代顺序keys().next().value能拿到最先插入的key也就是最久未使用的那个。之前没有意识到这一点的人可能会用数组自己维护顺序导致时间复杂度到O(n)笔试时容易超时。当年我刷这道题时用的是对象双向链表比较麻烦。后来发现Map的迭代顺序特性直接用Map就把代码精简了很多。你也别觉得这是投机取巧能用好语言特性本身就是能力。3.4 编程题三扁平数据结构转树形结构贝壳的第三题是给一组扁平数据转换成树形结构。数据长这样const arr [ { id: 1, parentId: 0, name: 集团 }, { id: 2, parentId: 1, name: 房产事业部 }, { id: 3, parentId: 1, name: 装修事业部 }, { id: 4, parentId: 2, name: 北京链家 }, { id: 5, parentId: 4, name: 望京门店 } ];要求转换成嵌套树{ id: 1, name: 集团, children: [ { id: 2, name: 房产事业部, children: [ ... ] }, { id: 3, name: 装修事业部, children: [] } ] }最常见的写法是借助哈希表一次性遍历function buildTree(arr) { const map new Map(); const roots []; arr.forEach((item) { map.set(item.id, { ...item, children: [] }); }); map.forEach((node) { if (node.parentId 0) { roots.push(node); } else { const parent map.get(node.parentId); if (parent) { parent.children.push(node); } } }); return roots; }这里有两个注意点。第一不能直接修改原对象否则会污染arr里的数据最好用展开运算符创建新对象。第二遍历map时因为父节点一定先于子节点被遍历到实际不一定但map.forEach按插入顺序执行而插入顺序由arr决定所以如果子节点在父节点之前出现需要两次循环处理更稳妥的做法是先遍历一遍创建所有节点第二遍再建立关系。我上面这个写法恰好是先创建全部节点再建立关系稳妥。这个题贝壳考得比较实用因为房地产这种业务里组织架构、区域门店、楼盘数据的层级关系非常常见面试官后来还追问了我删除节点时怎么处理子节点属于扩展问题。3.5 编程题四函数防抖第四题相对简单手写函数防抖。题目描述大概是多次触发事件时只有最后一次触发后等待wait时间才执行若在等待期内再次触发则重新计时。function debounce(fn, wait) { let timer null; return function(...args) { const context this; if (timer) { clearTimeout(timer); } timer setTimeout(() { fn.apply(context, args); }, wait); }; }这题的坑不在于防抖本身而在于this指向和参数的保留。如果你写计时器函数时直接用fn(...args)在严格模式下this会变成undefined所以要用apply传入正确的context。另外一个常见的坑是节流与防抖的区别答题时要是被要求说明差异就是防抖是“只执行最后一次”节流是“每隔一段时间执行一次”。4. 笔试中的常见问题与排查技巧实录4.1 编程题提交时的“隐形杀手”我考完翻了牛客评论区发现很多人讨论的不是题目本身而是“本地跑得好好的在线一提交就0分”。这个现象太普遍了我总结出几个原因第一输入输出格式。在线判题系统对输出格式非常敏感多一个空格、少一个换行都可能被判错。例如要求输出一行数字你却在每行末尾多打了空格很可能被判格式错误。建议输出时用数组.join( )或.join(\n)统一处理避免手写拼接出问题。第二递归爆栈。如果笔试时用了递归解法但数据范围比较大比如n100000递归深度过大可能导致栈溢出。贝壳的数据范围不会特别激进但稳妥起见能用迭代就多用迭代。第三没有处理非法输入。比如题目允许输入为空数组你直接arr[0]取值就会报错。笔试前养成习惯凡是访问数组、字符串前先判断长度。4.2 客观题中最容易翻车的几处细节选择题里我也踩了两个坑分享出来希望你绕开。一个是parseInt和map的经典组合题[1, 2, 3].map(parseInt)的输出结果。很多人以为是[1, 2, 3]实际是[1, NaN, NaN]。原因在于map会把数组索引作为第二个参数传给parseInt所以第二次调用是parseInt(2, 1)进制1是无效的返回NaN。这个题贝壳每次基本都会考属于送分题但每年都有人错。另一个是数组的sort比较细节默认排序会把元素转成字符串后按字典序排序所以[10, 9, 100].sort()输出是[10, 100, 9]。要用(a, b) a - b才能按数字升序。这种题让人很头大但恰恰是基础扎实与否的分水岭。4.3 考试环境问题记录我在笔试过程中还遇到了一次断网重连的情况。好在这类在线笔试平台都有断网保护重连后还能继续答题。但还是建议你提前做好两件事一是确保网络稳定尽量使用有线网络连接二是提前把浏览器清理好关闭无关标签页免得切屏误判。平台监控通常比较严格有些后台会记录你点击了多少次其他窗口小心驶得万年船。另外贝壳笔试是允许使用本地IDE的。建议你提前装好VS Code或WebStorm配好Node.js环境考场上编写代码会更顺手。网页编辑器虽然能用但语法提示和缩进体验确实差一些。5. 考后复盘这套题给后续面试的启示5.1 贝壳笔试风格总结贝壳找房前端笔试的整体风格可以概括为“基础 工程 场景”。它不会故意刁难你但题量摆在那里知识面稍微有盲区就会明显感觉到时间不够用。对比其他大厂贝壳更看重实际开发中常用到的技能比如Promise、事件循环、数据结构转换这些跟做业务时遇到的场景高度重合。我后来通过面试了解到贝壳的团队规模大业务线复杂前端要支撑房源展示、交易流程、经纪人工作台等大量页面所以对基础JS和后端交互理解要求比较高。笔试里出现LRU、扁平转树这类题其实就是在模拟它们日常开发中数据处理的场景。5.2 抓住准备的核心方向如果你打算投贝壳我的建议是复习时抓住这几个方向手写题多练Promise系列all、race、retry、防抖节流、深拷贝、LRU、数组扁平化、树形转换。这些都是高频中的高频。JS基础概念过一遍作用域、闭包、this、原型链、事件循环。不要只背概念要能在看代码时判断输出结果。网络和浏览器HTTP缓存、状态码、输入URL到页面展示的过程。选择题基本年年考。工程化基础webpack核心概念、模块化演进AMD/CommonJS/ESM不用特别深入但基本概念要清楚。我当时复习用的方法是把牛客上贝壳前两三批的笔试题找出来刷一遍加上LeetCode前端高频题每天手写5个函数。坚持两周后笔试时看到编程题基本都有思路整个过程没太慌乱。5.3 如果笔试发挥不好后面还能补救吗贝壳的笔试结果通常是系统评分加上人工查看编程题部分尤其重要。如果你选择题做得一般但编程题AC了两道以上还是能进面试的。反过来编程题一道都没过简历再漂亮也可能被卡住所以编程题优先级一定要拉满。万一笔试没过也不用灰心。秋招是长跑贝壳不同批次、不同岗位比如房产事业部、装修事业部有时会分开招人可以密切关注官网和牛客的动态后续批次还有机会。我个人的体会是贝壳这套笔试题难度本身并没有夸张到劝退但它的广度会给你一个非常真实的基础水平反馈。很多同学考前盲目刷hard题反而忽略了最基础的Promise执行顺序和原型链结果在选择题上丢了大分。刷题要有方向基础不牢地动山摇这句话放在贝壳笔试题上再贴切不过。