百度2015前端笔试题复盘:那些被框架藏起来的底层基础
2026/8/29 21:03:03 网站建设 项目流程

前端圈里如果有人让我推荐一份能检验基本功的旧试卷,百度2015年前端研发笔试卷一定排在我的前三。这份卷子出题的年代,React 才刚刚发布 0.14,Vue 还停留在 1.0,jQuery 在绝大多数项目里仍然是顶梁柱,ES6 正式定稿也不过是半年前的事。那时候没有那么多“工程化八股”,面试官考的就是你今天在一个框架里面被藏起来的原生能力。最近我把这份卷子翻出来,用过来人的视角重新做了一遍,发现很多题在今天依然是面试现场的常客。这篇文章不打算给你背题答案,而是把考点背后的原理、当年的思考方式、以及我复盘时踩过的坑一起摊开讲一讲。适合正在准备前端面试、或者想补一补底层基础的人收藏。

1. 这份卷子的历史坐标:2015年前端开发到底考什么

1.1 当时的前端技术栈,和现在完全是两个物种

2015年前端技术栈和今天差别很大。主流项目里 jQuery 负责 DOM 操作,requirejs 或 seajs 做模块化加载,Grunt 或 Gulp 管构建流程;AngularJS 1.x 在企业级应用里有相当多的装机量,React 0.14 刚把 React 和 ReactDOM 拆开,Vue 1.0 还靠手写模板和指令打天下。CSS 方面,less 和 sass 已经普及,但很多人还在用 CSS reset 解决浏览器默认样式差异。

那时候没有 TypeScript 的大规模普及,也没有 Vite 这种秒起 dev server 的工具。写页面要考虑 IE8,甚至个别项目还要兼容 IE6。大环境下,笔试的考察目标和现在不一样:考的是你脱离框架之后,还能不能直接操作浏览器。所以这份卷子很少问“某个框架的某个 API 怎么用”,而是把一套原生 JS、浏览器机制、网络协议的东西摆在桌面上,让你现场写。

1.2 出题人视角:要筛选什么样的人

百度那几年的研发笔试,风格以基础扎实和代码硬核著称。前端岗位的笔试卷里,JS 语言硬核题、浏览器机制题、算法题占据大部分篇幅,CSS 布局和兼容性也有一席之地。这背后有很清晰的筛选逻辑:前端是离用户最近的一道关卡,页面渲染快不快、交互稳不稳、兼容性好不好,都要靠工程师的基础素养兜底。

所以这套卷子考察的维度可以拆成四块:

考察维度常见题型目的
原生 JS 功底作用域、闭包、原型、this看语言基础牢不牢
浏览器 API事件、DOM、Ajax、跨域看能不能直接操作浏览器
页面与工程意识CSS 布局、缓存、性能优化看页面还原与上线思维
逻辑算法数组、字符串、排序看代码思路是否清晰

这套筛选逻辑放到今天仍然成立。现在很多面试题表面上考 React 原理或 Vue 响应式,追问下去还是作用域、事件循环、HTTP 缓存那套东西。框架换了,底层的地基没有换。

2. JS 语言基础与浏览器 API:第一大题的高频考点拆解

2.1 变量提升与作用域陷阱

流传的回忆版试卷里,第一道 JS 题往往从作用域开始。一道经典题是这样的:

var name = '百度'; function test() { console.log(name); var name = '前端'; console.log(name); } test();

很多人第一反应是输出'百度''前端',但标准答案是undefined'前端'。原因在于var声明会提升到当前函数作用域顶部,但赋值不提升。test 函数内部的var name会把整个函数的 name 绑定局部化,所以第一个console.log访问的是还没赋值的局部变量,不是全局变量。

这个知识点在 ES6 之后被letconst修正了一部分,但理解提升机制仍然重要。因为面试官常常会追问:如果外层没有var name,直接name = '前端',结果是什么?那就是在非严格模式下创建了一个全局变量,严格模式会直接抛 ReferenceError。写代码时一定要避免这种隐式全局变量,这是代码规范中最基本的一条。

2.2 闭包、this 和类型判断

闭包题是这套卷子的常客。最典型的是循环绑定事件:

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

输出是 5 个 5。因为 setTimeout 回调执行时,循环早已结束,i已经被改成了 5。解决办法是用立即执行函数包一层,把每次的i传进闭包:

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

ES6 出来后可以直接用let,但当年笔试卷上标准答案就是 IIFE。考官想看到的不只是答案,而是你知道闭包能保存变量状态的机制。

this 指向题也很高频。下面这段是常见套路:

var name = 'global'; var obj = { name: 'obj', getName: function () { console.log(this.name); } }; obj.getName(); // obj var fn = obj.getName; fn(); // global

关键点是:this 在函数调用时确定,而不是定义时确定。obj.getName()是作为 obj 的方法调用,this 指向 obj;fn()是普通函数调用,this 指向全局对象。2015 年的严格模式背景下,还需要知道fn()在严格模式里 this 是 undefined,输出会直接报错。

类型判断是另一个全场必考。面试官通常不会直接问“如何判断数组”,而是让你写一个通用方法:

function isType(value, type) { return Object.prototype.toString.call(value) === '[object ' + type + ']'; }

这里为什么用Object.prototype.toString.call而不用typeof?因为typeof []返回'object'typeof null也返回'object',根本无法区分。而Object.prototype.toString能准确返回'[object Array]''[object Object]''[object Null]'这类细粒度信息。这个思路在今天的 Vue 源码里都在用,底层判断逻辑几十年没变过。

2.3 事件体系:绑定、冒泡与委托

浏览器事件相关题目占了不小比重。第一层让你手写兼容绑定:

function bindEvent(el, type, fn) { if (el.addEventListener) { el.addEventListener(type, fn, false); } else if (el.attachEvent) { el.attachEvent('on' + type, fn); } else { el['on' + type] = fn; } }

标准浏览器用 addEventListener,IE8 及以下用 attachEvent。注意 attachEvent 回调里的 this 指向 window,这是老 IE 一个容易踩的坑。现在写代码基本不用兼容 IE8 了,但这个封装逻辑体现了兼容性思维:降级策略要一层层铺,先走标准,再走私有 API,最后兜底。

第二层通常是事件委托。一道常考题是:一个 ul 下有几千个 li,点击 li 时弹出对应索引,怎么绑定最高效?答案是用委托:

document.getElementById('list').addEventListener('click', function (e) { var target = e.target; if (target.nodeName.toLowerCase() === 'li') { console.log(target.textContent); } }, false);

事件委托的原理是利用冒泡,把子元素的处理器统一挂在父元素上。好处有两个:一是减少事件绑定数量,内存更省;二是新插入的 li 自动获得事件处理能力,不用重新绑定。这个思想在现代框架的合成事件里也有体现,React 早期版本就把事件统一挂到根节点,靠事件代理统一分发。

2.4 DOM 操作的高性能写法

笔试里会给一段频繁操作 DOM 的代码,让你优化。常见场景是循环往页面插入数据:

var list = document.getElementById('list'); for (var i = 0; i < 1000; i++) { var li = document.createElement('li'); li.textContent = i; list.appendChild(li); }

问题在于每次 appendChild 都可能触发一次布局计算,数据量一大页面就卡。优化方案是先用 DocumentFragment 把节点积累起来,最后一次插入:

var list = document.getElementById('list'); var fragment = document.createDocumentFragment(); for (var i = 0; i < 1000; i++) { var li = document.createElement('li'); li.textContent = i; fragment.appendChild(li); } list.appendChild(fragment);

DocumentFragment 是一个轻量的文档片段,它不会触发回流,直到整体 append 到真实 DOM 时,浏览器只做一次插入。类似思路还有缓存 DOM 引用、批量改 class 而不是逐条改 style、用textContent而不是innerHTML等。这些细节点滴积累起来,就是面试官想看到的性能敏感度。

3. CSS 布局与浏览器兼容:第二大题里的老江湖题

3.1 盒模型与 IE 之间的纠葛

CSS 题目里第一关永远是盒模型。标准盒模型的width只包含内容区,padding 和 border 另外计算;IE 怪异盒模型的width则把 padding 和 border 都算进去。这道题的经典考法有两种:一种是问box-sizing: border-box的作用,另一种是问如何兼容 IE 下盒模型差异。

实际项目里,很多团队会设置全局的box-sizing: border-box

*, *:before, *:after { box-sizing: border-box; }

好处是布局时不用反复加 padding 再担心宽度溢出。但要注意,2015 年时 IE7 不支持这个属性,IE8 支持但必须声明 doctype。当年写代码要兼顾 IE 的话,通常会额外用条件注释加载一个 polyfill 或针对 IE 写补偿样式:

<!--[if lt IE 8]> <div class="hack">你的浏览器需要升级</div> <![endif]-->

条件注释是 IE 特有的语法,其他浏览器会把它当作普通注释。这种兼容方案在现在很多前端项目里已经消失,但面试官仍可能会问,因为它考的是你是否理解浏览器从渲染到脚本执行的差异根源。

3.2 两栏三栏布局与 BFC

布局题里最常出现的是两栏和三栏布局。两栏经典考法是固定宽度侧栏加自适应主栏:

<div class="wrapper"> <div class="sidebar">固定宽度</div> <div class="main">自适应</div> </div>
.wrapper { overflow: hidden; } .sidebar { float: left; width: 220px; height: 200px; } .main { margin-left: 220px; height: 200px; }

这套方案的关键在于 BFC。.sidebar浮动后脱离文档流,主栏用margin-left让出位置;.wrapper设置overflow: hidden用来清除浮动,防止父容器高度塌陷。BFC 的全称是 Block Formatting Context,也就是块级格式化上下文,触发方式包括overflow非 visible、floatposition为 absolute 或 fixed、display为 inline-block 或 table-cell 等。

BFC 的作用很多:清除浮动、阻止外边距折叠、阻止元素被浮动元素覆盖。面试官问“BFC 是什么”时,如果只背定义会很苍白,最好能现场画一棵简单的布局树然后解释清楚。2015 年的最佳实践里还有圣杯布局和双飞翼布局,两者都是三栏布局中间自适应、两边固定的方案,核心差别在于左右栏如何放到中间栏两侧。现在 flex 已经普及,但理解老方案的实现原理,能帮你更清楚现代 flex 布局为什么更方便。

3.3 水平垂直居中的主流方案

居中题是 CSS 题里最容易被小瞧的一道。最基本的水平居中大家都懂,margin: 0 auto加上固定宽度,或者text-align: center作用于内联元素。垂直居中就麻烦很多,不同场景不同解法。

固定宽高元素可以用绝对定位加负 margin:

.parent { position: relative; } .child { width: 200px; height: 100px; position: absolute; top: 50%; left: 50%; margin-top: -50px; margin-left: -100px; }

不固定宽高时,用transform: translate(-50%, -50%)更优雅:

.child { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }

2015 年要考虑 transform 在低版本 IE 的兼容性,所以面试时最好把两种方案都写出来,然后说“如果不需要兼容 IE8,用 transform;需要兼容,用负 margin”。这种取舍意识比单纯背代码更值钱。现在用 flex 一行就能解决:

.parent { display: flex; align-items: center; justify-content: center; }

但很多面试官仍然会问老方案,因为 flex 在某些极端场景下也有坑,比如容器宽度不够时内容被压缩、多行文本换行问题。懂得多个方案,才能在不同场景里选合适的。

3.4 兼容性处理与移动端 Rem 的萌芽

笔试卷里还有一类题是给你一段 CSS,让你指出兼容性问题。比如opacity: 0.5在 IE8 不支持,需要写成filter: alpha(opacity=50);又比如border-radius在 IE8 不支持,需要兜底。这种题目考的不是背诵 hack,而是你在实际项目中会不会主动查 Can I Use,有没有为降级体验做方案。

移动端在这时已经开始爆发,rem适配是加分项。rem 相对于根元素的 font-size,通常配合 JavaScript 动态设置根字号来实现等比例缩放。而且 2015 年也是 flex 兼容性开始成熟的时候,很多团队已经开始在移动端尝试 flex 布局。笔试卷上如果出现“如何做移动端适配”,能说出 rem + media query + viewport 三件套,基本就能拿到大部分分。

4. 算法题与代码题:给搜索引擎工程师看的编程底子

4.1 字符串与数组的手写题

前面聊的都是语言和浏览器层面,算法题则是筛人最狠的一关。回忆版里最常见的是数组去重、字符串反转、回文判断、排序、扁平化。数组去重标准答案之一:

function unique(arr) { var result = []; for (var i = 0; i < arr.length; i++) { if (result.indexOf(arr[i]) === -1) { result.push(arr[i]); } } return result; }

进一步追问总是“时间复杂度是多少”。这个版本时间复杂度是 O(n^2),因为 indexOf 是线性查找。更优方案是用对象做哈希表,把时间复杂度降到 O(n):

function unique(arr) { var seen = {}; var result = []; for (var i = 0; i < arr.length; i++) { var key = typeof arr[i] + arr[i]; if (!seen[key]) { seen[key] = true; result.push(arr[i]); } } return result; }

这里为什么要拼上typeof?因为数字 1 和字符串'1'如果用同一个 key 会被误判成相同元素。这个细节能区分出你是背题还是真懂。

字符串反转也是一道高频题:

function reverse(str) { return str.split('').reverse().join(''); }

追问会来一句“如果字符串里有中文或表情符号呢”。这时候 split('') 按 UTF-16 码元拆分可能把一个 emoji 拆成两个乱码。ES6 里可以用Array.from(str)或扩展运算符处理。面试官往往很吃这种边界思考能力。

4.2 快速排序与斐波那契的数学思维

快速排序是笔试白板题的重头戏。手写实现不难,但要从分治思想讲起:

function quickSort(arr) { if (arr.length <= 1) return arr; var pivotIndex = Math.floor(arr.length / 2); var pivot = arr[pivotIndex]; var left = []; var right = []; for (var i = 0; i < arr.length; i++) { if (i === pivotIndex) continue; if (arr[i] < pivot) { left.push(arr[i]); } else { right.push(arr[i]); } } return quickSort(left).concat(pivot, quickSort(right)); }

我实际面试时的体会是:写出快排不难,难的是把时间复杂度的推导说清楚。平均 O(n log n),最坏 O(n^2)。最坏情况出现在基准值选得不好,比如每次选到最大或最小值。所以回答时最好主动提一句:“如果数据接近有序,这种固定取中间的基准值会有退化风险,可以用三数取中法优化。”这就是加分项了。

斐波那契一般会让你写出多种实现。最朴素递归:

function fib(n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2); }

这个版本会有大量重复计算,n 稍微大一点就卡住。优化方案是加缓存或直接用迭代:

function fib(n) { if (n <= 1) return n; var prev = 0; var curr = 1; for (var i = 2; i <= n; i++) { var next = prev + curr; prev = curr; curr = next; } return curr; }

这种从指数级优化到 O(n) 的过程,比单纯写对代码更重要。面试官想看的是你有没有意识到性能问题,而不是背了一个标准答案。

4.3 手写继承与 Ajax 封装

笔试卷里会直接让你写继承。很多人只知道 ES6 的 class,但 2015 年的标准答案还是函数式继承。组合继承是使用最广的:

function Parent(name) { this.name = name; } Parent.prototype.sayName = function () { console.log(this.name); }; function Child(name, age) { Parent.call(this, name); this.age = age; } Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child;

这里有几个点要讲清楚:Parent.call(this, name)是为了继承实例属性,Object.create(Parent.prototype)是为了继承原型方法同时不让父构造函数反复执行,最后还要修正constructor指向。如果写Child.prototype = new Parent()也可以,但父构造函数会多执行一次,可能引入多余的实例属性。考官追问时,知道这层差异很重要。

Ajax 封装题基本是送分题,但写出完整状态判断还是能筛掉一批人:

function ajax(options) { var xhr = new XMLHttpRequest(); xhr.open(options.method || 'GET', options.url, true); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { options.success && options.success(xhr.responseText); } else { options.fail && options.fail(xhr.status); } } }; xhr.send(options.data || null); }

代码本身不难,但要能解释 readyState 从 0 到 4 分别代表什么,为什么 304 也要算成功。2015 年很多人还在用 jQuery 的$.ajax,能把原生 XMLHttpRequest 写清楚,本身就是基本功的证明。

5. 性能优化和 HTTP:压轴综合题的得分要点

5.1 页面加载性能优化的标准套路

压轴题通常是一道综合题:给一个访问量很大的页面,现场分析有哪些性能问题并提出优化方案。标准答案可以分几个层面展开。第一个层面是减少请求数量,把多个 JS/CSS 文件合并压缩,图片用雪碧图(CSS Sprite)把小图合成一张大图,减少 HTTP 往返。现在 HTTP/2 普及后,合并文件策略有所松动,因为多路复用让并发请求成本下降,但 2015 年的标准答案依然是合并。

第二个层面是静态资源 CDN 化。把 JS、CSS、图片放到 CDN,让用户从就近节点加载。这个点不是背概念,而是要说明为什么 CDN 快:减少物理距离、多节点缓存、降低源站压力。

第三个层面是懒加载与按需加载。图片在滚动到视口附近时再加载,模块在路由切换时再异步加载。2015 年常见实现是监听 scroll 事件,然后判断元素与视口的距离;现代方案则用 IntersectionObserver,原理类似,但 API 更友好、性能更高。能把老方案到新方案的演进说出来,会让面试官觉得你是有实践经验的。

5.2 HTTP 缓存机制与状态码

缓存题目几乎必考。HTTP 缓存分为强缓存和协商缓存。强缓存由ExpiresCache-Control控制,命中后直接从本地读取,不发请求。Expires是绝对时间,容易受本地时钟影响;Cache-Controlmax-age是相对时间,更准确。二者同时存在时Cache-Control优先。

协商缓存由Last-ModifiedETag控制。Last-Modified是文件最后修改时间,配合请求头If-Modified-Since使用;ETag是文件内容的实体标识,配合If-None-Match使用。服务器收到校验后,如果资源没变就返回 304,浏览器继续用自己的缓存。

实际项目里,标准做法是 HTML 文件用协商缓存或 no-cache,静态资源带哈希文件名后走强缓存,哈希变了文件名变了,自然就能让浏览器加载新资源。这道题如果展开讲,可以把200 from cache304200 OK的区别也带上。2015 年的题目喜欢问“一个静态文件长期不更新,客户端如何及时拿到新版本”,答案就是文件名指纹。

5.3 跨域方案和 XSS、CSRF 安全底线

跨域题目和前端安全经常放在一起考。最经典的跨域方案是 JSONP。它的原理是利用 script 标签不受同源策略限制,动态创建一个 script,服务端返回一段 JavaScript 代码,调用你指定的回调函数:

function jsonp(url, callbackName, done) { var script = document.createElement('script'); script.src = url + '?callback=' + callbackName; window[callbackName] = function (data) { done(data); document.body.removeChild(script); }; document.body.appendChild(script); }

JSONP 的缺点也很明显:只支持 GET、错误处理能力差、还有安全问题。后来主流方案变成 CORS,通过服务端返回Access-Control-Allow-Origin头来控制跨域资源访问。2015 年面试时如果只说 CORS 名字是不够的,需要知道简单请求和预检请求的区别,知道什么时候会触发 OPTIONS 预检。

安全题里最常考的是 XSS 和 CSRF。XSS 的核心是不信任用户输入,渲染用户内容时不能直接拼 HTML,必须转义或用 textContent。CSRF 的核心是跨域请求伪造,预防手段包括校验 Referer、使用 CSRF Token、双重 Cookie 验证。面试官如果问“一个评论功能如何防 XSS”,标准回答是:入库前校验过滤、输出时进行 HTML 转义、使用 CSP 白名单限制脚本执行。这些内容放在一套笔试卷里,能看出工程师是不是有上线安全思维。

6. 我拿这套卷子复盘时踩过的坑

6.1 做题时的时间分配与得分策略

我重新做这套卷子时,最大的感悟是:笔试题不是每道都要写满,但每一道都要先写思路。比如算法题如果一时写不出完整代码,可以先把分治或动规的递推式写出来,让审题人看到你的方向正确。相反,有些人死磕一道 20 分的算法题,导致后面 CSS 和缓存这种稳拿分的题没时间做,非常可惜。

我的建议是拿到卷子先快速浏览所有题目,按难易分类。JS 语言基础题和 CSS 题属于基础分,先做;算法题每道限时,超过 15 分钟没思路就写伪代码,继续做下一道;性能优化和安全题属于综合题,放在最后展开。这样能保证整张卷子的得分面是完整的。

6.2 那些年最容易翻车的 JS 坑

复盘时我发现,很多错误不是因为不会,而是因为细节踩坑。第一个坑是parseInt的进制问题。老版本浏览器里parseInt('08')可能会因为 ECMAScript 把以 0 开头的字符串当作八进制解析,返回 0。所以规范写法永远是parseInt('08', 10)。第二个坑是数组的sort()默认按字符串编码排序,[10, 9, 2].sort()得到的是[10, 2, 9],必须传入比较函数。

第三个坑更隐蔽:手写防抖函数时丢失了 this。

function debounce(fn, delay) { var timer = null; return function () { clearTimeout(timer); timer = setTimeout(function () { fn.apply(this, arguments); // 错误:这里的 this 不是调用方 }, delay); }; }

正确写法需要在闭包外层保存 this:

function debounce(fn, delay) { var timer = null; return function () { var context = this; var args = arguments; clearTimeout(timer); timer = setTimeout(function () { fn.apply(context, args); }, delay); }; }

这类细节在笔试卷上不会直接提醒你,但如果你提交的代码有这种问题,面试官一眼就能看到。我后来带新人时,常拿这份卷子做基线测试,凡是在防抖 this 上栽过跟头的,后续做 React 事件绑定也容易出错。因为底层都是同一个问题:回调函数执行时的 this 指向。

6.3 这套题对今天面试的启示

现在的前端面试确实不再原样考 IE 兼容和 attachEvent,但这份卷子的内核没有过时。变量提升与闭包仍然是作用域链的基础,事件委托仍然是 React 合成事件的原型思路,HTTP 缓存仍然是性能优化的主战场,XSS 和 CSRF 在任何时代都是安全底线。

我看到近几年不少候选人简历里写着熟悉 Vue 或 React,但问到底层响应式原理、虚拟 DOM diff 策略、组件渲染性能优化时容易卡壳。原因就是框架把太多底层细节封装掉了,很多人直接跳过了原生基础。如果你正在准备 2026 年的前端面试,我强烈建议把这类旧试卷当作“底层体检”来做:React 源码读不进,先翻闭包;Vue 响应式理解不了依赖收集,先看原型链和 Object.defineProperty;构建速度上不去,先去把 HTTP 缓存和浏览器渲染机制吃透。

额外提醒一句:现在面试官很吃追问能力。你答“用 transform 做居中”时,顺手补一句“因为 transform 不触发重排,GPU 合成更快”,就是一个有经验工程师该有的敏感度。这套试卷考的是知识点,你真正要修炼的是把知识点串成体系的能力。

最后再分享一个我自己反复使用的小技巧。拿到任何一份旧笔试题,我都不会直接看答案,而是先限时一小时自己写一遍,再对照当年的标准答案和现代写法分别做一版。一版是“如果我在 2015 年会怎么写”,一版是“如果我现在会怎么做”。对比两个版本,你就能清楚看到哪些是新工具带来的语法糖,哪些是真正不可替代的基础能力。前端变化再快,作用域、原型、渲染、缓存、安全这五件事,始终值得反复嚼。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询