
从2015年走到今天前端圈的变化快得吓人。但当我最近翻出那份“百度2015前端研发笔试卷”一边复盘一边感慨好多题目放到现在依然能拦住一批面试者。倒不是说题目多难而是它考的很多底层认知恰恰是如今浮躁的框架学习中容易被忽略的部分。这篇博文不打算做成“考古”而是把这份流传较广的笔试卷当作一面镜子。我会按当年的考点结构整理出核心题目和解题思路再对照现在的技术环境复盘一遍哪些答案被时代改写了哪些底层逻辑十年未变。无论你是准备面试的求职者还是想打牢基础的前端开发者都能从这套旧题里找到值得反复咀嚼的东西。1. 一套旧试卷的价值它定下了前端岗位的“能力坐标系”1.1 这套卷子到底考什么网上流传的百度2015前端研发笔试卷题目版本很多但核心考点高度一致。我梳理下来基本集中在四块JavaScript语言基础、CSS与页面布局、浏览器兼容、性能优化与工程化意识。客观题以选择题和填空题为主主观题几乎占了一半经常让你“写代码实现一个功能”或者“简述某个方案的优缺点”。印象比较深的几类题给出代码片段写出输出结果考察类型转换、闭包、this指向、原型链。让你实现一个事件委托、防抖节流、数组去重之类的小函数。CSS部分会让写出某布局方案的代码或者解释浮动塌陷、盒模型差异。浏览器兼容方向问IE6/IE7常见bug与解决办法那个年代这是必考题。性能优化是拉开分差的地方题目更开放考察知识面是否成体系。那张卷子整体风格偏“基础但深入”没有太多偏题怪题。它的意图非常明显不指望你用过多少框架而是看你有没有吃透JS这门语言本身以及能不能在资源受限的浏览器环境里写出稳定代码。1.2 技术栈变了考点为什么没全变2015年是个特殊节点。ES6还在草案阶段jQuery依然是很多团队的主力库React刚发布不久Vue 1.0刚刚问世前端工程化正从grunt/gulp向webpack过渡。百度在那个时间点出这样一份笔试其实已经在筛选“能适应未来变化”的人。十年后再看很多当年纠结的API已经消失了比如bind的实现手写、addEvent/attachEvent差异、CSS hack写法。但试卷背后测试的能力模型没有变对语言机制的理解、对运行时行为的预判、对页面加载全链路的认知。这有点像考驾照。十年前考的是“看后视镜、打转向灯、观察路况”十年后路况变了、车变了但“看后视镜”这个动作仍然是必考项。因为驾驶的本质从来不是操作某个具体按钮而是建立起对车辆和环境的掌控感。前端也一样框架可以换但JS的真相、渲染的本质、网络的边界永远是地基中的地基。2. 语言基础JS必考题放到今天依然实用2.1 数据类型与类型转换别小看那道“变态题”JS类型转换是2015年笔试卷里的“送命题”。我记得有一道流传很广的题var a [] ![]; console.log(a); // 很多第一次见到的人会懵。我们来拆解一下![]的优先级高于先算![]。空数组是对象对象转布尔值恒为true所以![]是false。于是题目变成[] false。在比较对象和布尔值时先做ToNumber转换false变成0[]先ToPrimitive转为空字符串空字符串再ToNumber变成0。两边都是0结果就是true。这类题目背后真正考的是隐式转换规则、ToPrimitive过程、相等比较的优先级顺序。放在实际开发中这类“坑”真的会以各种姿势出现比如if (0 ) // true容易造成误判 if (null undefined) // true规范的宽松相等设计之一所以现在写代码我几乎全部使用严格相等。判断相等不要依赖隐式转换判断NaN用Number.isNaN判断对象相等比较引用判断数组内容要用循环或every。不写歧义代码是对自己和同事负责。2.2 闭包与作用域链一张纸递出去以后闭包几乎是每次笔试必考。经典题目for (var i 0; i 5; i) { setTimeout(function() { console.log(i); }, 1000); } // 输出什么答案是连续5个5。原因在于var声明的i是函数级作用域循环结束后i已经变成5而setTimeout里访问的是同一个i。理解的钥匙是作用域链每个函数在创建时会保存一个指向父级作用域的引用执行时沿着这个链路查找变量。闭包就是“函数 函数定义时所在的作用域”。这里5个定时器函数共享同一个外层作用域所以看到的是同一个i。更好的写法是for (let i 0; i 5; i) { setTimeout(function() { console.log(i); }, 1000); }let每次循环都会创建一个新的绑定闭包捕获的是当次迭代的i。这个知识点放到今天并没有过时反而是理解useEffect、useCallback依赖项为什么容易引发闭包陷阱的前提。实际开发中闭包最大的价值是封装状态和实现私有变量function createCounter() { let count 0; return { increment: function() { count; }, get: function() { return count; } }; }对外暴露的接口可以访问count但外部无法直接修改。这种封装思路在组件状态管理里随处可见。2.3 this指向与原型链找链条也找调用方式this指向是JS新手最容易翻车的地方。2015年试卷里这类题完全不缺现在面试问框架原理也绕不开。我总结过一套判断this的优先级按顺序套用new绑定构造函数里的this指向新创建的对象。call/apply/bind显式绑定this指向传入的第一个参数。方法调用obj.method()里this指向obj。默认绑定非严格模式下指向globalThis严格模式下是undefined。箭头函数不绑定this它继承定义时外部作用域的this相当于“词法绑定”。原型链同样重要一道高频题是function Parent() { this.name parent; } Parent.prototype.sayHello function() { console.log(hello this.name); }; var child new Parent(); child.sayHello();这里child自身没有sayHello但通过__proto__找到Parent.prototype上的方法。查找过程是实例自身属性 → 构造函数的prototype → 再往上层原型 → 直到Object.prototype → null。当年笔试要求“手写继承”我一般都写组合继承把构造继承和原型链各用一半function Child(name) { Parent.call(this, name); } Child.prototype Object.create(Parent.prototype); Child.prototype.constructor Child;用Object.create避免直接赋值导致子类和父类原型引用同一个对象这是一个值得记一辈子的细节。放到现在class语法已经非常成熟直接extends就行但底层原型链机制依然是理解React类组件、Vue响应式原理的前提。2.4 异步与事件循环从setTimeout到微任务那年头的异步题基本是“闭包定时器回调”的组合。但现在再问异步一定会涉及微任务和宏任务的输出顺序console.log(1); setTimeout(function() { console.log(2); }, 0); Promise.resolve().then(function() { console.log(3); }); console.log(4);正确输出是1、4、3、2。原因是同步代码先执行然后清空微任务队列最后才取宏任务队列的第一个任务执行。Promise.then属于微任务setTimeout属于宏任务。这个模型到现在依然是JS运行时的核心调度逻辑。我在实际调Bug时凡是遇到“为什么这个回调没按预期顺序执行”的问题都会回归事件循环这张图去推演一遍。调试异步问题的第一原则永远不要用“我以为的顺序”去代入代码而是画出微任务和宏任务的排队过程。3. 页面与样式兼容性驱动的旧时代但“为什么”依然值钱3.1 盒模型与浮动塌陷2015年做页面最烦的就是“差2像素”。试卷里这部分题很细典型的标准盒模型和IE盒模型有什么区别浮动元素造成的父容器高度塌陷怎么处理标准盒模型的width只包含内容区IE盒模型的width包含了content、padding、border。解决差异最简单的方式是加box-sizing: border-box这已经是现代开发的默认操作。浮动塌陷的原因是浮动元素脱离文档流父容器不再计算它的高度所以高度塌了。清除浮动的方式我常用clearfix.clearfix::after { content: ; display: block; clear: both; }现在flex和grid已经普及浮动布局用得少了但“为什么会塌陷”的思维模型依然有用。凡是脱离文档流的元素都可能导致类似问题比如absolute定位元素超出父容器边界。理解了根源才能在不同场景下举一反三。3.2 垂直居中与三种布局方案2015年的一个经典问题是“实现一个元素水平垂直居中要求兼容IE8”。当时很多人第一反应是absolute margin.center { position: absolute; left: 50%; top: 50%; width: 200px; height: 100px; margin-left: -100px; margin-top: -50px; }这是最“稳”的一招但需要知道元素宽高。还有一种不用算宽高的.center { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); }但transform在IE8不支持当年只能算“加分项”。如今我会直接推荐flex.parent { display: flex; justify-content: center; align-items: center; }两栏布局经典方案是圣杯/双飞翼布局核心都是利用margin或padding为中间栏留出空间再通过负边距把左右栏拉上去。这套方案如今已不常用但它的价值在于强化一个认知CSS布局本质上是在处理“文档流边距定位”的关系理解了这个组合拳你就不会被任何一个新框架绑架。3.3 浏览器兼容IE时代的判断手段2015年还在处理IE6、IE7、IE8兼容。最常见的操作是条件注释!--[if lt IE 9] script srchtml5shiv.js/script ![endif]--还有各种CSS hack比如*开头只对IE6生效_前缀只对IE7生效。写代码前要先确认产品要支持哪些浏览器版本再决定是“渐进增强”还是“优雅降级”。放到今天浏览器兼容的重点从IE转向了不同内核的差异、不同平台的自适应、以及移动端碎片化适配。但当年的解题思路没有过时不是“我写了一套代码走天下”而是“我先确定目标环境再选择最合适的方案”。前端开发永远要面对环境差异这个意识比具体hack写法值钱得多。4. 性能与工程化试卷里的加分项今天的必修课4.1 性能优化题的经典答法2015年面试问“怎么优化页面性能”主流答案大多围绕雅虎军规展开减少HTTP请求、合并压缩JS/CSS、图片使用CSS雪碧图、启用CDN、加缓存、延迟加载。思路本身很朴素但非常成体系。现在性能优化的重心已经从“请求数量”转向“核心用户体验指标”比如LCP最大内容绘制、TBT总阻塞时间、CLS布局偏移。工具的颗粒度也完全不同。举个例子图片优化。2015年的惯用方案是CSS雪碧图把很多小图标拼成一张大图减少请求数。今天雪碧图依然在部分场景使用但显然更主流的是响应式图片、AVIF/WebP格式、懒加载、以及基于CDN的图片实时压缩裁剪服务。这种变化背后是网络环境和设备性能的巨变4G/5G普及后带宽不再是第一瓶颈但移动端设备的CPU和内存依然有限所以“怎么减少主线程的阻塞”反而成为更核心的问题。4.2 静态资源与缓存策略当年笔试有个高频问法前端静态资源如何做版本管理标准答案是文件名加版本号更新版本时地址变化CDN缓存的旧文件自然失效。后来的工程化实践进步了一步文件名带内容hash内容变则hash变内容不变则hash不变。这样既能长期缓存未变化的资源又能保证更新后的资源被及时加载。这是webpack、Vite等工具默认在做的“持久化缓存”策略。我在实际项目里见过不少反面案例没有给文件名加hash只靠“配置文件加时间戳”强行刷新结果多次发布后用户浏览器加载到旧版本出现线上Bug。这种事排查起来非常痛苦因为代码是新的但浏览器加载的是旧文件。写代码时一定要想清楚强缓存和协商缓存的边界文件名hash是前端资源“不可变版本”信任的基石。4.3 工程化雏形模块化与构建工具2015年的模块化题目常考AMD和CMD区别要求手写模块加载逻辑。当时requirejs和seajs是主流本质上都是解决“多个script标签依赖顺序混乱”的问题。现在的构建工具已经进化了数轮webpack、Rollup、Vite、esbuild、Turbopack各领风骚。但模块化背后的核心逻辑没变依赖管理、作用域隔离、按需加载、编译转换。我建议每个前端人都手动看一次webpack的产物代码看看ESM、CommonJS到底被编译成了什么样子。这个“亲眼所见”的过程比背任何文档都有用。工程化能力的本质不是“会用某个工具”而是“理解工具解决了什么问题以及为什么这么解决”。有这个认知打底你的技术栈更新成本会低很多。5. 如果今天的试卷再考一次哪里最不一样5.1 从“操作DOM”到“声明数据”当年题目里我见过一道“给页面上一组列表绑定点击事件要求高性能实现”。标准答案是事件委托document.getElementById(list).addEventListener(click, function(e) { if (e.target.tagName LI) { // do something } });但今天的框架应用场景这类问题已经“不太需要回答了”。React里你写onClickVue里你写click框架帮你完成了大多数事件处理。面试时更常问的是虚拟DOM为什么快diff算法怎么优化闭包陷阱怎么避免这个变化本质上是思维方式的迁移从“命令式地操作界面”变成“声明式地描述状态和界面的关系”。我在实践中最大的感受是声明式开发明显提升了代码的可维护性让我可以把注意力从“怎么改DOM”转移到“数据从哪来、数据怎么变”。但底层机制仍然值得深挖。知道事件委托的原理才能理解React 17事件挂载到根容器、而不是document上的设计原因知道虚拟DOM的树对比过程才能明白为什么渲染列表需要唯一的key。5.2 从“web安全基础”到“供应链安全”2015年的安全题基本就是XSS、CSRF概念题问一下怎么防护。XSS防范的核心是转义和CSPCSRF防范的核心是Token验证和SameSiteCookie属性。到了今天安全题的范围明显扩大了。前端依赖npm包已经是常态一个底层包被投毒影响面可能覆盖成千上万个项目。所以面试时如果你能提到依赖锁文件、供应链审计、第三方脚本监控、SRI子资源完整性校验这些点会明显拉开差距。我实际维护项目时养成的习惯是安装新依赖前先看一眼包周下载量、最近更新时间、维护者数量CI流程里加入安全审计命令对第三方加载的JS加integrity和crossorigin属性。安全不是单一某个操作而是一整套默认习惯。5.3 从“兼容IE”到“多端与性能预算”当年写CSS要兼容IE现在前端已经进入“多端齐发”时代PC浏览器、移动端H5、小程序、桌面应用、嵌入式WebView。作为一个前端你要面对的不只是“浏览器版本差异”而是“运行环境完全不同”的问题。同样是性能优化PC端可以放开了用大体积素材移动端就要考虑流量、内存、渲染帧率。小程序端则有一整套自己的性能优化规则比如setData的大小、分包策略。2015年没多少人谈“性能预算”但今天的团队越来越把它当作一票否决项一个首屏体验超标的版本就算功能完整也不允许上线。我会在项目初始化阶段就设定基准比如“移动端首屏JS体积不超过200KBLCP不超过2.5秒”每次上线前用Lighthouse或CI中的性能脚本进行回归对比。这种从“感觉慢”到“用指标约束”的转变是我眼中这十年前端最大的一次成长。最后说一点个人体会。翻完这套2015年的试卷我倒觉得不用惋惜“当年背的API都过时了”。真正值得留下的是那些问题的提问方式本身——它逼你想清楚“输入是什么、过程是什么、边界在哪里”。从前端入门到进阶最稳的路径永远是先把JS这本“内功心法”读透再去看框架各种“招式”。如果你准备找前端工作与其去背海量面试八股不如认真找一套旧题不查资料、不看答案真刀真枪地写一遍然后把自己的答案和当时的题目解析逐条对照。你会发现很多“不会做”并不是因为你不知道某个API而是某些底层概念没有真正串起来。我自己的习惯是每过一两年就把这些基础题重新做一遍。每次都能感受到哦原来我理解的深度又不一样了。这套旧试卷某种程度上成了我前端路上的一面镜子。