news 2026/8/29 23:36:13

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
百度2020校招前端笔试题全解析:从JS基础到框架原理

1. 试卷整体观察:一场典型的“大厂海选”式考察

拿到这份百度2020校招Web前端工程师笔试卷(第一批),第一反应是熟悉。如果你经历过那个年代的校招季,应该对这种卷子有印象——它不像社招那样深挖某个方向的细节,而是更像一张“能力扫描仪”,在有限的两个小时里,尽可能把候选人的知识面、基本功、临场写代码的能力都过一遍。

先说结论:这份试卷的核心命题思路可以概括为三句话——基础不牢,地动山摇;框架再熟,原理要懂;算法不练,笔试白干。

从题型编排来看,2020年这个时间节点非常典型。那会儿 Vue 2 正处于巅峰期,Vue 3 还在 beta 阶段,React 16.8 刚推出 Hooks 不到一年,前端工程化已经全面普及,但还没有像现在这样人手一个 monorepo、动不动就上 WASM。所以试卷里的题目很好的反映了那个时期的行业状态:ES6+ 已经全面普及,框架原理开始成为必考点,HTTP 和浏览器缓存依然是大头,手写代码题从“能不能写出来”变成了“写得好不好、边界处理全不全”。

我在帮很多人复盘这种试卷时发现一个规律:凡是能把下面这四块准备扎实的人,基本都能过笔试关——

  • JavaScript 语言本身,尤其是原型链、闭包、异步这套东西;
  • 浏览器工作原理和网络基础,缓存、渲染机制、HTTP 协议;
  • 框架的实际使用,但更重要的是设计思想;
  • 手写代码能力和基础算法思维,不要求你 ACM 金牌,但基本的数据结构和常见题的套路你得有。

这篇文章就按这个逻辑,把这份试卷的考点逐层拆开,每一块我都给出当年的题目回忆、正确的解题思路,以及我踩过/见过别人踩过的坑。不管你是正在准备校招的应届生,还是工作几年想回头补基础的同行,这份拆解应该都能帮你把“学过的知识”串成“能答题的能力”。

提示:所有题目均为基于该批次考试常见考点的回顾与重构,并非原始试卷的逐字复刻,但考点覆盖和出题思路完全对齐那一年的真实风格。

2. 基础能力篇:JS 语言特性与浏览器原理,绕不开的“内功”

2.1 原型与原型链:面试官最爱问、最见功底的一题

几乎每份前端笔试卷都有原型链的题,百度这份也不例外。原题的大意是考察对prototype__proto__Function/Object之间关系的理解,要求写出下面这段代码的输出:

function Person(name) { this.name = name; } Person.prototype.sayHello = function() { console.log('Hello, ' + this.name); }; const p = new Person('Alice'); console.log(p.__proto__ === Person.prototype); console.log(Person.prototype.constructor === Person); console.log(p instanceof Person); console.log(Person.__proto__ === Function.prototype); console.log(Object.prototype.__proto__);

答案是:truetruetruetruenull

这道题看似简单,却能刷掉不少人。很多人能答出前四个,但第五个容易卡住——Object.prototype的原型是null,因为它是原型链的终点。我见过不少候选人卡在这一步,原因不是不知道,而是平时写业务代码时根本没机会接触这些底层关系。

我的建议是,必须亲手画一遍完整的原型链图。不要只看文章,拿张纸把PersonFunctionObject之间的关系画出来,标注每条边的方向。画完之后你自然能理解为什么Function.__proto__ === Function.prototype也成立——这是新手最容易纠结的点:Function 是一个函数,同时它又是通过自身的 prototype 构造出来的,形成了一个看似自指的结构。

这类题拿分的重点是理解三个规则:new构造的对象以构造函数的prototype为原型;实例通过__proto__寻找原型链上的属性和方法;所有函数的__proto__都指向Function.prototype,而它本身也是一个函数,所以Function.__proto__又指向它自己。

2.2 作用域与闭包:从一道改错题聊起

笔试卷中闭包的考察非常多样。我记得那份卷子里有一道这样的改错题,现在看依然很经典。题目给出了一个循环注册事件的代码,要求修正让它输出正确的索引:

for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 1000); }

如果直接跑,输出的是 5 个5。原因是setTimeout的回调函数访问的是同一个变量i,当回调真正执行时循环早已结束,i已经变成 5。修正方案有三个层面,从传统到现代:

第一个方案是 ES5 的经典做法,用立即执行函数创建独立作用域:

for (var i = 0; i < 5; i++) { (function(j) { setTimeout(function() { console.log(j); }, 1000); })(i); }

第二个方案是把var改成let,因为letfor循环的每个迭代中都会重新绑定变量:

for (let i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 1000); }

第三个方案是用bind传参:

for (var i = 0; i < 5; i++) { setTimeout(console.log.bind(console, i), 1000); }

这道题真正的价值不在“怎么改”,而在“为什么这样改就能好”。当年我在准备面试时,用红笔在书边写了一句现在看来很朴素的话:只要回调函数访问的变量是在父级作用域声明的,而这个回调整体被延迟执行,就必须警惕变量捕获的时机问题。let之所以能解决,是因为它每次迭代都会创建新的词法环境;IIFE 之所以能解决,是因为它通过函数参数把值“拍了个快照”传给内部作用域。

2.3 浏览器缓存机制:网络题里的“送分题”与“送命题”

二面之前的那场笔试,网络部分的题量一直不小。百度这份卷子考了缓存,题目大意是:页面加载一个app.js,第一次请求返回 200,后续刷新时返回 304,请问服务端怎样设置响应头才能让浏览器在强缓存生效期间不发起网络请求?

这里很多人会混淆强缓存和协商缓存,我直接给标准答案和背后的逻辑。

强缓存有ExpiresCache-Control两个头。Expires是 HTTP/1.0 的老方案,值是绝对时间,缺点是客户端时间不准就会失效。Cache-Control是 HTTP/1.1 的方案,常用值是max-age=3600,表示相对时间,3600 秒内直接读本地缓存,不发任何请求。

协商缓存靠的是Last-Modified/If-Modified-SinceETag/If-None-Match。第一次请求时服务端返回ETag(资源内容的哈希标识),浏览器缓存下来;下一次请求时浏览器带上If-None-Match头,服务端对比当前资源的 ETag,如果没变则返回 304,浏览器继续用本地缓存;如果变了则返回 200 和新的资源。

回到题目本身:如果希望资源在生命期内完全不发请求,应该设置Cache-Control: max-age=86400这种强缓存;如果希望每次重新验证,则设置Cache-Control: no-cache(注意no-cache的意思是“使用前必须验证”,而不是“不缓存”),配合ETag使用。还有一个容易混淆的no-store,那才是真正的不允许任何缓存。

当年我见过很多答案把no-cacheno-store搞混,也有人不知道 304 本质上依然是“一次网络请求”,只是响应体很小。这些都属于原理没吃透的表现。笔试不是让你背字段,而是在考察你能不能解释清楚一次完整的请求-缓存-命中流程。

前端开发中缓存策略的坑特别多。我个人的经验是:指纹化的静态资源(比如app-8d3c2f.js)用强缓存,让浏览器直接本地读;HTML 文件用no-cache配合协商缓存,保证页面更新后能快速拿到最新的资源引用列表;如果上线后发现问题需要紧急修复,就改版本号或 hash。说到底,缓存策略的核心是一个“平衡”,在性能和及时性之间找那个最适合业务场景的落点。

3. 框架原理篇:会调用只是入门,懂设计才算合格

3.1 Vue 响应式原理:经典的 Object.defineProperty 时代

2020 年的 Vue 题基本围绕 2.x 展开,核心考点集中在一个地方:响应式系统是如何工作的。原题大概要求先解释v-model的原理,再手写一个简单的响应式实现。

v-model的本质是语法糖。在 Vue 2 中,它等价于:value@input。对<input>元素来说,v-model会绑定 value 属性,同时监听 input 事件,事件触发时把输入值写回数据。对自定义组件来说,v-model默认监听的是value属性和input事件,但你也可以通过model选项修改这两个默认名称。

更深层的响应式原理分三块:

  • Observer:遍历数据对象的属性,用Object.defineProperty把每个属性都改成 getter/setter。当属性被访问时(get),收集当前的依赖(Watcher);当属性被修改时(set),通知所有依赖进行更新。
  • Dep:依赖管理器,每个响应式属性都有一个 Dep 实例,负责收集和通知 Watcher。
  • Watcher:观察者。组件渲染时生成一个 Watcher,当它访问了某个响应式属性后,就被收集进该属性的 Dep 里。

一道经典的面试追问是:Vue 2 为什么无法检测到数组和对象的新增属性?因为Object.defineProperty是在初始化时对已有属性做的拦截,新增属性并没有经过这个转换,所以不是响应式的。Vue 2 为此提供了Vue.set方法,它会手动调用observe为新属性建立响应式,并触发更新。数组方面,Vue 2 直接替换了pushpopshiftunshiftsplicesortreverse这七个方法,在调用后手动触发更新,所以通过下标赋值(arr[2] = x)也是无法触发更新的。

可以现场写一个简易版响应式系统,帮助理解核心机制:

function observe(obj) { if (typeof obj !== 'object' || obj === null) return; Object.keys(obj).forEach(key => { let value = obj[key]; observe(value); const dep = new Dep(); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { if (Dep.target) dep.add(Dep.target); return value; }, set(newValue) { if (newValue !== value) { value = newValue; observe(newValue); dep.notify(); } } }); }); } class Dep { constructor() { this.subs = []; } add(sub) { this.subs.push(sub); } notify() { this.subs.forEach(sub => sub.update()); } } Dep.target = null;

这段代码是 Vue 2 响应式系统的骨架,笔试时能默写出来,基本就能证明你是真的理解了原理,而不是背了两篇面经就上考场。

3.2 React Hooks 与设计理念:踩在新旧交替的节点上

2020 年 React 16.8 的 Hooks 已经发布一年多,笔试卷里出现相关题目很正常。我记得有题是让解释useStateuseEffect的原理,并要求从类组件思维切换到 Hooks 思维。

这道题考查的点很实际:Hook 的调用顺序为什么不能变?这背后是 Hooks 的核心实现机制——React在组件渲染时按调用顺序把每个 Hook 的状态存在一个链表里。如果条件渲染导致某个 Hook 在某次渲染中没有被调用,后续所有 Hook 拿到的状态就会错位。所以 Hook 必须在组件顶层调用,不能写在条件语句里。

useEffect的题也很容易出变种,让你比较不同的依赖数组写法。最常见的一个坑:依赖数组传空数组[]和完全不传依赖数组,效果完全不同。空数组表示只执行一次(mount 时执行,unmount 时清理);不传表示每次渲染都执行;传入[a, b]表示 a 或 b 变化时执行。

笔试时可以用一个对比表格来回答,清晰又高效:

写法执行时机适用场景
不传依赖每次渲染后执行不推荐,容易死循环
[]仅 mount 时执行初始化请求、一次性订阅
[a, b]a 或 b 变化时执行依赖特定状态的副作用

我见过一些候选人,讲了 Hooks 的用法,却说不清为什么比类组件好。这里有一个很本质的差异:类组件的生命周期方法是“按时间点”组织的,而 Hooks 是“按功能”组织的——把相关的逻辑放在同一个 Effect 里,而不是分散在componentDidMountcomponentDidUpdatecomponentWillUnmount三处。这也是 Hooks 设计上最重要的价值。

3.3 框架题答题技巧:原理题要有层次

在校招笔试里,框架原理题的作答是可以有套路的,不是背标准答案就行。我的经验是,不管遇到什么原理题,都按“用法 → 实现机制 → 设计价值”三层结构来写。

第一层交代基本用法,让阅卷人确认你用过这个功能;第二层深入源码机制,比如 Vue 的依赖收集或 React 的 Fiber 调度,证明你不只是 API 调用者;第三层谈设计价值,为什么 Vue 2 用Object.defineProperty而不是 Proxy(那会儿 Proxy 的兼容性和性能还不理想),为什么 React 要引入 Fiber(因为旧版递归调用的 stack reconciler 无法中断,长任务会阻塞渲染线程)。这种答题结构基本能把一道原理题变成你的加分项。

我特意提醒一句:笔试回答原理题时,不要只写概念,一定要给出关键代码片段。哪怕不完整,哪怕是伪代码,也能向面试官传递“我确实写过、深入过”的信号。空对空的概念陈述在笔试中得分率非常低。

4. 手写代码篇:从“会做”到“做得对”,差的是一堆边界条件

4.1 防抖和节流:手写频率控制函数的标准答案

百度这轮笔试考了一道手写防抖(debounce)和节流(throttle),这是前端手写题里的常青树。题目要求实现一个通用的防抖函数,并说明防抖和节流的区别。

先给出标准实现:

// 防抖:触发后等待 delay 毫秒,如果期间再次触发则重新计时 function debounce(fn, delay) { let timer = null; return function(...args) { if (timer) clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); timer = null; }, delay); }; } // 节流:每隔 interval 毫秒最多执行一次 function throttle(fn, interval) { let last = 0; return function(...args) { const now = Date.now(); if (now - last >= interval) { last = now; fn.apply(this, args); } }; }

防抖和节流的区别,可以用一个生活中的例子讲明白。防抖就像电梯关门——如果不断有人进来,电梯就不断重新等;只有停止进人之后才开始关门。节流就像游戏人物的技能冷却——技能用完之后必须等冷却时间结束才能再用,不管你怎么按。

笔试时虽然只要求写出实现,但我会建议多写两行注释把适用场景标清楚:防抖适合输入框搜索(用户停止输入后才发请求)、窗口 resize 后的计算;节流适合滚动事件监听、按钮点击防重复提交。

这道题的高分关键在边界处理上:第一,返回的函数要保留this指向,所以用fn.apply(this, args);第二,需要考虑是否要立即执行一次——有的业务场景要求第一次点击立即生效,后续点击防抖,这也是面试官喜欢追问的变种。

4.2 深拷贝:递归容易,完美的边界处理很难

手写深拷贝也是那张卷子的重点题目,现在仍然是前端面试的高频考题。题目要求手写一个深拷贝函数,能够拷贝对象、数组、函数、日期对象等基本类型。

一个符合面试要求的深拷贝实现如下:

function deepClone(source, map = new WeakMap()) { if (source === null || typeof source !== 'object') return source; if (source instanceof Date) return new Date(source); if (source instanceof RegExp) return new RegExp(source.source, source.flags); if (map.has(source)) return map.get(source); const target = new source.constructor(); map.set(source, target); Object.keys(source).forEach(key => { target[key] = deepClone(source[key], map); }); return target; }

为什么要用 WeakMap?因为深拷贝会遇到循环引用的问题。比如对象a.self = a,如果不处理,递归会无限循环直到栈溢出。WeakMap 可以记录已经拷贝过的对象,再遇到时直接返回拷贝副本,从而打破循环。

还要注意 Date 和 RegExp 需要特殊处理,因为它们不是普通对象,直接遍历属性会丢东西。函数在这份代码里会被原样返回——函数在深拷贝场景下一般复用引用即可,没必要递归克隆,这是行业里的通行做法。

我见过很多候选人能写出递归遍历对象的版本,但很少意识到要处理循环引用。这就是“会写”和“写对”的区别。准备手写题时,千万别满足于“能跑”,要多问自己几个问题:如果是数组怎么办?日期对象怎么办?两个对象互相引用怎么办?把这些边界情况都兜住了,才是笔试拿满分的水平。

4.3 手写 Promise.all:异步题里的“必刷题”

手写Promise.all是我建议每个求职者必须滚瓜烂熟的题。百度这份卷子也考了类似方向的异步题,要求实现一个Promise.allPromise.race的简易版本。

一个符合规范的Promise.all实现如下:

function promiseAll(promises) { return new Promise((resolve, reject) => { const results = []; let count = 0; for (let i = 0; i < promises.length; i++) { Promise.resolve(promises[i]).then(value => { results[i] = value; count++; if (count === promises.length) resolve(results); }).catch(reject); } }); }

这里有两个关键点。第一,Promise.all的返回值顺序必须保持和输入顺序一致,所以要用results[i] = value而不是push。第二,Promise.all的机制是“只要有一个 reject,整个 Promise 立即 reject”,所以.catch(reject)直接透传错误。

如果用Promise.all处理并发请求,有一个场景需要特别注意:假设你有 10 个请求,其中一个必挂,但你希望其他 9 个的结果仍然能返回。这时不能直接加 catch,而要在每个 promise 上加 catch,保证返回的始终是 resolved:

const safePromises = promises.map(p => p.catch(err => null)); const results = await Promise.all(safePromises);

手写这道题的价值不仅在笔试本身,工作中尤其是做并发控制和批量请求时,其实经常要自己组合类似逻辑,理解底层实现细节会非常有帮助。

4.4 JS 实现事件总线:藏在业务代码里的“暗题”

很多时候笔试卷不会直接考 EventEmitter,但会以“实现一个简单的发布订阅模式”的方式出现。百度那轮笔试中有一道类似的题目,要求实现一个事件总线,支持onoffemit三个方法。

参考实现:

class EventBus { constructor() { this.events = {}; } on(eventName, fn) { if (!this.events[eventName]) { this.events[eventName] = []; } this.events[eventName].push(fn); } off(eventName, fn) { if (!this.events[eventName]) return; if (!fn) { delete this.events[eventName]; return; } this.events[eventName] = this.events[eventName].filter(item => item !== fn); } emit(eventName, ...args) { const fns = this.events[eventName]; if (fns && fns.length) { fns.forEach(fn => fn(...args)); } } }

发布订阅模式在业务代码中的典型应用是:组件间通信(特别是兄弟组件)、多个模块监听同一个数据变更、全局状态变化通知。这类题手写难度不大,但要注意off的边界情况:不传fn时表示清空该事件的所有订阅;传了fn时只移除指定的那个回调。还能再延伸一步,加一个once方法:注册时包一层函数,触发后自动解绑,这也是 EventEmitter 里非常常见的能力。

手写题部分是我的建议是:考前把“防抖、节流、深拷贝、Promise.all/race、发布订阅、数组去重、数组扁平化、柯里化”这八道题全部写成肌肉记忆,闭眼能默写。2020 年的题库和今天相比差异不大,这些题的出镜率实在太高了,值得背到如同条件反射的程度。

5. 算法与逻辑题:不拼竞赛金牌,但拼基础思维

5.1 数组去重与字符串处理:送分题也可以拿满分

百度那场笔试的编程题部分没有出特别难的算法题,整体难度对非 ACM 方向的同学比较友好。但“友好”不代表可以轻视,反而说明这类题的区分度完全在于细节和边界。

数组去重,这道题从大一学 JS 起就见过。但在笔试里,它考察的就不只是“会用 Set 而已”:

// 基础版:利用 Set const unique = arr => [...new Set(arr)]; // 进阶版:保留首次出现的顺序 const unique = arr => arr.filter((item, index) => arr.indexOf(item) === index); // 更通用:按属性去重(比如给对象数组按 id 去重) const uniqueBy = (arr, key) => [...new Map(arr.map(item => [item[key], item])).values()];

第三个版本的原理值得多说一句:Map的 key 是唯一的,用item[key]作为 key,item作为 value 放进 Map,后出现的同名 key 会覆盖先出现的 value,所以最后values()取出来的是每个 key 对应的最后一条记录。如果希望保留第一条记录,可以反着遍历。

刷笔试卷时,很多人会跳过这种“太简单”的题。但根据我的经验,大厂的校招笔试卷恰恰喜欢在这种简单的题目上设置隐性考察点——比如要求处理超大数组时的性能、要求保持顺序、要求按对象属性去重。这些变体考察的是你“能不能把工具方法背后的原理迁移到新场景”。

5.2 经典算法:二叉树层次遍历与快速排序

算法部分,那轮笔试考了二叉树的层次遍历,这也是前端面试中出现频率极高的数据结构题。因为 DOM 树本身就是一棵多叉树,前端的很多场景天然适合用树结构建模。

用 JavaScript 实现二叉树的层次遍历(BFS),最直接的方式是结合队列:

function levelOrder(root) { if (!root) return []; const result = []; const queue = [root]; while (queue.length) { const levelSize = queue.length; const currentLevel = []; for (let i = 0; i < levelSize; i++) { const node = queue.shift(); currentLevel.push(node.val); if (node.left) queue.push(node.left); if (node.right) queue.push(node.right); } result.push(currentLevel); } return result; }

注意levelSize必须在每层开始前先取出来,因为循环过程中queue长度会变化。如果直接拿queue.length作为循环次数,就会出现“一层当成多轮处理”的问题。这是这道题最经典的错误点,没有之一。

快速排序也是高频题目。前端算法题的难度一般不会要求你手写红黑树,但快排、二分查找、链表反转、二叉树遍历这类基础算法,是必须达到默写水平的。

function quickSort(arr) { if (arr.length <= 1) return arr; const pivot = arr[Math.floor(arr.length / 2)]; const left = []; const right = []; const equal = []; for (let i = 0; i < arr.length; i++) { if (arr[i] < pivot) left.push(arr[i]); else if (arr[i] > pivot) right.push(arr[i]); else equal.push(arr[i]); } return [...quickSort(left), ...equal, ...quickSort(right)]; }

这个版本用空间换时间,简单直接,适合笔试作答。面试时如果被追问,可以再聊原地快排和Array.prototype.sort的内部机制(V8 里数组长度小于 10 用插入排序,大于等于 10 用快速排序的变体 TimSort)。

5.3 算法题的备考策略:前端岗位不需要“奥数冠军”

有些同学一想到算法就害怕,觉得自己数学不好,可能不适合大厂。但说实话,前端校招笔试中的算法题,整体难度是低于后端同级别岗位的。核心考察范围就是:数组、字符串、链表、栈/队列、二叉树、哈希表、基础排序搜索、简单的动态规划(如爬楼梯、斐波那契数列、最大子序和)。

我的备考建议是抓住高频题清单,而不是盲目刷 300 道题。针对前端校招,下面这张清单基本够用:

分类高频题目必会程度
数组两数之和、三数之和、最大子序和、合并区间必须会
字符串最长回文子串、无重复字符最长子串、字符串翻转必须会
链表反转链表、环形链表判断、合并两个有序链表必须会
二叉树前/中/后序遍历、层次遍历、最大深度必须会
排序快排、归并排序、堆排序思想必须会
动态规划爬楼梯、打家劫舍、零钱兑换尽量会

刷题时不要只想着“做出来”,每道题做出来后多问一句:时间复杂度和空间复杂度是多少?有没有更优解?边界条件是什么?我自己复习时有一个习惯,每道题都在代码旁边备注时间和空间复杂度,这样笔试时写答案也会自然地带上复杂度分析,阅卷人一眼就知道你是有算法素养的。

6. 综合应用题与工程化:笔试里的“场景题”怎么拿分

6.1 前端性能优化:一道说烂了但依然能拉开差距的题

综合题部分是校招笔试最有区分度的地方,因为它考察的是你“有没有做过东西”。百度那份卷子里有一道前端性能优化题,大意是:一个移动端页面首屏加载很慢,从网络、渲染、资源加载三个角度,分析可能原因并给出优化方案。

这道题怎么答,直接反映候选人的真实项目经验。光是罗列“压缩 JS、合并文件、CDN 加速”这种话术已经拿不到分了,满大街都是这么写答案的。你需要的是逻辑清晰、有优先级、能自圆其说的分层方案。

网络层面:

  • 减少请求数量:合并小文件、雪碧图(那个年代还常用)、HTTP/2 多路复用(对请求数限制的缓解);
  • 减小传输体积:开启 gzip/Brotli 压缩,JS/CSS 压缩混淆,图片用 WebP 格式;
  • 利用缓存:指纹化静态资源配强缓存,HTML 配协商缓存;
  • 使用 CDN 把静态资源分发到离用户更近的节点。

渲染层面:

  • 减少首屏阻塞:script标签加deferasync,CSS 内联关键路径样式;
  • 减少重排重绘:合并 DOM 操作、使用requestAnimationFrame、避免频繁读取布局属性;
  • 懒加载:图片用loading="lazy",路由级代码分割,首屏只加载必要的 JS。

资源层面:

  • 代码分割:Webpack 的splitChunks把公共依赖单独打包,利用浏览器缓存;
  • Tree Shaking:消除未使用的导出代码,减少打包体积;
  • 服务端渲染或预渲染:如果 SEO 和首屏体验要求高,可以考虑 SSR,但这会引入额外的维护成本,要结合团队情况评估。

答题时我建议只写骨架,但是每个点后面一定跟一句“为什么”和“怎么做”,这样才能展现出你真的踩过坑。比如光写“图片懒加载”没有意义,写成“首屏外的图片用loading="lazy"或 IntersectionObserver 实现按需加载,因为首屏图片请求会堵塞资源带宽,拖慢 LCP”就完全不一样了。

6.2 Webpack 构建题:工程化能力的基本盘

2020 年 Webpack 在前端工程化中几乎是垄断地位,笔试卷考 Webpack 是常规操作。百度那份卷子有一道题,大意是:项目构建速度太慢,你能从哪些方向优化?

参考答案可以从四个维度展开:

  • Loader 范围缩小include限定src目录,exclude排除node_modules,避免不必要的文件处理;
  • 缓存babel-loader开启cacheDirectory,或使用cache-loader;Webpack 5 的持久化缓存可以跨构建复用;
  • 并行构建thread-loader把耗时的 loader 放到 worker 池中并行处理;HappyPack(当时常用,现在维护少了);
  • 减少模块解析resolve.alias指定库的入口文件、resolve.extensions减少无意义的后缀名尝试、module.noParse跳过不需要解析的库(比如 jQuery 这类 UMD 包装的库);
  • 开发时用devtool: 'eval-cheap-module-source-map':生产构建不用 devtool 或只生成简化的 source map,因为 source map 的生成非常费时间。

如果你没用过 Webpack 但用过 Vite,可以把 Vite 的思路套进来:开发环境中使用原生 ESM 按需编译,预构建依赖缓存,避免全量打包。原理是相通的,都是为了“少干活、干快活”。

笔试里如果时间有限,不要求你把所有配置项都默写出来,但至少要写出一个“明确的问题定位 + 对应的配置项 + 配置后的预期效果”结构,这比零散地写十个优化点强得多。

6.3 场景设计题:设计一个图片懒加载组件

综合设计题也是大厂笔试的常客。我记得百度那轮有一道比较偏实践的题:要求实现一个图片懒加载组件,可以是伪代码或描述思路。

要拿高分的话,可以从这几个层面展开:

HTML/CSS 层面:

  • 图片不直接设置src,用>const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.classList.add('loaded'); observer.unobserve(img); } }); }, { rootMargin: '0px 0px 200px 0px', // 提前 200px 预加载 }); document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));

    问题难点在于:图片全部加载完之前要防止重复触发,所以用unobserve在加载后取消观察;rootMargin可以控制预加载的距离,这样用户体验更好。另外要处理 JS 文件加载前页面已经滚动到目标位置的情况——需要先手动检查一次所有图片的位置,而不是只靠后续滚动事件触发。

    这类场景题的高分秘诀是:不要只写一个实现,要把“为什么用 IntersectionObserver 而不是 scroll + getBoundingClientRect”讲出来——前者不需要监听高频滚动事件,性能更好;同时要主动聊边界情况,比如禁用 JS 时的降级方案(<noscript>或直接输出src)、错误加载的兜底等。这些细节才是真正打动阅卷人的地方。

    7. 备考复盘与建议:用一份真题卷校准自己的知识体系

    7.1 考完别急着对答案,先做“错题归因”

    很多同学做完笔试卷后,就急着去搜标准答案,对完就过了。这样其实效率很低。我更推荐的做法是:每道错题都归因到知识模块,再决定下一轮复习重点。

    比如这道题错了,可能是因为:

    • JavaScript 语法细节掌握不牢(作用域、闭包、异步时序);
    • 框架原理只停留在 API 使用层面,没看过源码;
    • 算法题刷少了,看到题目没有思路;
    • 工程化经验不足,场景题不知道从哪下手;
    • 纯粹是时间分配问题,简单题花太久,大题没时间写。

    把这五类原因列出来,对照自己的错题分布,就能很清楚看到短板在哪。我的经验是:时间分配问题最可惜,也最好解决——考前做两三次完整模拟,卡着时间练,就能避免“前面的题写太细,后面的题来不及”的尴尬。

    7.2 各类知识模块的优先级排序

    结合多份大厂校招笔试卷的共性,我给准备中的同学一个优先级参考:

    优先级知识模块备考动作
    第一优先JS 基础原型链、闭包、异步、this;手写常见代码题
    第一优先浏览器与网络缓存、渲染机制、HTTP 常见状态码、跨域
    第二优先框架原理至少吃透一个框架的响应式/更新机制和生命周期
    第二优先手写代码8 道高频手写题背到肌肉记忆
    第三优先算法高频题刷 50-80 道,掌握套路即可
    第三优先工程化构建优化、性能优化方案,能说会道
    第四优先HTML/CSS少但精,Flex 布局、BFC、层级问题常考

    这份清单基本是按“性价比”排列的——同样的复习时间投进去,哪块分提升最快,就优先搞哪块。HTML/CSS 不是说不用看,而是它的考点相对集中,出题套路固定,花一天时间集中突破就够了,不需要长期投入。

    7.3 从笔试卷反推面试准备方向

    最后分享一个我自己很受益的思路——笔试不只是笔试,它事实上也是面试的浓缩预告。百度这份卷子暴露出的考察方向,和后续的面试题高度重合。笔试考了闭包和异步,面试大概率会追问“讲一下你项目中异步竞态是怎么处理的”;笔试考了缓存,面试官可能就会让你“描述一下从输入 URL 到页面渲染的完整过程”;笔试考了手写深拷贝,面试就可能问“深拷贝遇到循环引用怎么办,WeakMap 和 Map 的区别是什么”。

    所以做完一份真题卷,别急着扔掉。把里面的每一道题当成一个“考点的入口”,顺着它往下追问两三层,就是在为面试做准备。我当年就是靠这个方法,把一次失败的笔试变成了一本自己的面试题库——每道题都延伸出三到五个追问,每个追问都写成小卡片,回头翻一遍比刷十篇面经都有用。

    回到这份百度 2020 校招 Web 前端工程师笔试卷,它的价值不仅在于“公司名气大”,而在于它是一份非常标准的互联网大厂前端校招能力模型样本。把它的考点彻底搞懂,你的基本功、框架理解、编码能力、工程化思维都已经达到一个合格前端工程师的基准线了。剩下的,就是在真实项目中继续打磨,把“能做题”变成“能做事”。

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

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

STM32H7实现USB Mass Storage设备:从CubeMX配置到QSPI Flash+FATFS完整指南

最近在做一个大容量数据记录仪的项目&#xff0c;主控选的是STM32H723ZGT6&#xff0c;需要把设备采集的数据以U盘形式导出来。就是那种“插上USB线&#xff0c;电脑上直接多出一个移动磁盘&#xff0c;拖文件出来就行”的效果。折腾了小两周&#xff0c;把USB Mass Storage类设…

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

用友2017校招前端笔试题解析:JavaScript核心考点与浏览器原理

如果经历过那几年的校招季&#xff0c;大概率会对这类题目有印象&#xff1a;前端笔试不是让你默写 API&#xff0c;而是把 JavaScript 的语言特性、浏览器渲染机制、网络通信原理混在一张卷子里&#xff0c;考的东西看起来都是基础&#xff0c;但每一个选项都在挖坑。“用友 2…

作者头像 李华