news 2026/8/29 23:37:45

贝壳前端秋招笔试题复盘:JS基础考点与编程题实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
贝壳前端秋招笔试题复盘:JS基础考点与编程题实战解析

贝壳这套秋招笔试题,我是在第二批做的。整体感受是:题量不小,覆盖面偏基础但坑点多,选择题考得细,编程题不考偏题怪题,但非常考验编码熟练度和边界意识。如果你正准备贝壳或其他大厂的前端秋招,这篇复盘值得认真看完。

说说这份博文适合谁:马上要参加前端笔试的应届生,想看真实题型和难度的人,以及在备战过程中想系统补基础的同学。内容全部基于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()是同步执行的,所以会立即打印async2,await后面那行相当于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布局与BFC

CSS这批题里,Flex布局几乎是必考。贝壳对Flex的考查侧重于属性效果,比如flex-shrink的计算。我记得有道题问:一个Flex容器宽度400px,里面三个子元素flex-basis分别是200px、100px、100px,flex-shrink分别为1、2、3,问三个元素收缩后最终宽度是多少。

计算逻辑是:总溢出量 = (200+100+100) - 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")统一处理,避免手写拼接出问题。

第二,递归爆栈。如果笔试时用了递归解法,但数据范围比较大(比如n=100000),递归深度过大可能导致栈溢出。贝壳的数据范围不会特别激进,但稳妥起见,能用迭代就多用迭代。

第三,没有处理非法输入。比如题目允许输入为空数组,你直接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执行顺序和原型链,结果在选择题上丢了大分。刷题要有方向,基础不牢,地动山摇,这句话放在贝壳笔试题上再贴切不过。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/29 23:36:56

不止写代码:LLM非编码场景实战指南

打开 Hacker News 的时候&#xff0c;经常能看到“Ask HN”系列帖子里有人讨论 LLM 的各种用法。其中一条提问让我印象很深&#xff1a;“Do you use LLMs for non-coding related work?”也就是——除了写代码&#xff0c;你真的用大语言模型处理过日常工作吗&#xff1f;这个…

作者头像 李华
网站建设 2026/8/29 23:36:13

百度2020校招前端笔试题全解析:从JS基础到框架原理

1. 试卷整体观察&#xff1a;一场典型的“大厂海选”式考察 拿到这份百度2020校招Web前端工程师笔试卷&#xff08;第一批&#xff09;&#xff0c;第一反应是熟悉。如果你经历过那个年代的校招季&#xff0c;应该对这种卷子有印象——它不像社招那样深挖某个方向的细节&#x…

作者头像 李华
网站建设 2026/8/29 23:34:31

STM32H563/573 Debug Port调试接入:SWD与TrustZone安全机制详解

做 STM32H563/573 调试端口接入&#xff0c;或者叫“Access via Debug Port”&#xff0c;确实是一块容易让人踩坑的工作。别看 SWD 就是四根线的事情&#xff0c;到了带 TrustZone 的 Cortex-M33 平台上&#xff0c;Debug Port 的访问牵扯到芯片安全状态、调试认证、选项字节、…

作者头像 李华
网站建设 2026/8/29 23:33:32

MATLAB J-B检验实战:洪水受灾面积正态性分析与数据建模策略

1. 项目缘起&#xff1a;为什么需要检验洪水受灾面积的正态性&#xff1f;在水利工程、灾害评估和保险精算领域&#xff0c;洪水受灾面积是一个核心的评估指标。我们拿到一批历史洪水事件的受灾面积数据&#xff0c;第一反应往往是计算它的平均值、标准差&#xff0c;或者用这些…

作者头像 李华
网站建设 2026/8/29 23:27:50

金山办公2020校招前端笔试题解析:从JS基础到框架底层

每年七八月份都是校招笔试高峰&#xff0c;前端开发的岗位尤其卷。群里经常有学弟学妹拿着各类公司真题来问我&#xff0c;其中金山办公这套2020校招前端开发笔试题&#xff08;一&#xff09;&#xff0c;被问到的频率意外地高。按理说年份也不算近&#xff0c;但这套题的考察…

作者头像 李华
网站建设 2026/8/29 23:25:13

预训练模型与OpenAI API接入:新模型曝光下的开发者工程实践

最近技术圈又热闹了一轮&#xff1a;OpenAI 被曝出一个名为“Doug”的大规模预训练模型。很多读者在后台问我怎么看、要不要升级、能不能接入。坦白讲&#xff0c;目前关于“Doug”的公开信息非常有限&#xff0c;没有完整的技术报告&#xff0c;也没有官方模型卡&#xff0c;甚…

作者头像 李华