
前端圈里如果有人让我推荐一份能检验基本功的旧试卷百度2015年前端研发笔试卷一定排在我的前三。这份卷子出题的年代React 才刚刚发布 0.14Vue 还停留在 1.0jQuery 在绝大多数项目里仍然是顶梁柱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 之后被let和const修正了一部分但理解提升机制仍然重要。因为面试官常常会追问如果外层没有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 指向 objfn()是普通函数调用this 指向全局对象。2015 年的严格模式背景下还需要知道fn()在严格模式里 this 是 undefined输出会直接报错。类型判断是另一个全场必考。面试官通常不会直接问“如何判断数组”而是让你写一个通用方法function isType(value, type) { return Object.prototype.toString.call(value) [object type ]; }这里为什么用Object.prototype.toString.call而不用typeof因为typeof []返回objecttypeof 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; } }标准浏览器用 addEventListenerIE8 及以下用 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 classhack你的浏览器需要升级/div ![endif]--条件注释是 IE 特有的语法其他浏览器会把它当作普通注释。这种兼容方案在现在很多前端项目里已经消失但面试官仍可能会问因为它考的是你是否理解浏览器从渲染到脚本执行的差异根源。3.2 两栏三栏布局与 BFC布局题里最常出现的是两栏和三栏布局。两栏经典考法是固定宽度侧栏加自适应主栏div classwrapper div classsidebar固定宽度/div div classmain自适应/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、float、position为 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(opacity50)又比如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 缓存分为强缓存和协商缓存。强缓存由Expires或Cache-Control控制命中后直接从本地读取不发请求。Expires是绝对时间容易受本地时钟影响Cache-Control的max-age是相对时间更准确。二者同时存在时Cache-Control优先。协商缓存由Last-Modified和ETag控制。Last-Modified是文件最后修改时间配合请求头If-Modified-Since使用ETag是文件内容的实体标识配合If-None-Match使用。服务器收到校验后如果资源没变就返回 304浏览器继续用自己的缓存。实际项目里标准做法是 HTML 文件用协商缓存或 no-cache静态资源带哈希文件名后走强缓存哈希变了文件名变了自然就能让浏览器加载新资源。这道题如果展开讲可以把200 from cache、304、200 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); }; }正确写法需要在闭包外层保存 thisfunction 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 年会怎么写”一版是“如果我现在会怎么做”。对比两个版本你就能清楚看到哪些是新工具带来的语法糖哪些是真正不可替代的基础能力。前端变化再快作用域、原型、渲染、缓存、安全这五件事始终值得反复嚼。