2015百度前端笔试题解析:十年面试考点变迁与底层原理
2026/9/5 20:59:44 网站建设 项目流程

2015年百度前端研发笔试卷:从当年真题看这十年前端面试到底变了什么

前端这个圈子有个很有意思的现象:技术更新快到文档都跟不上,但面试题池子翻来覆去就那么几个老面孔在换皮。我前阵子翻到一份百度2015年的前端研发笔试卷,第一反应是“这题也太老了吧”,第二反应是“等等,这套题里的核心考点,现在面试还在考”。2015年是什么概念?那时候ES6刚进入草案阶段,Chrome 42才默认开启ES6的大部分特性;jQuery还在统治世界,AngularJS是MVVM的代名词,React刚发布1.0,Vue才刚刚1.0发布半年;Webpack 1.0也是这年推出的。这个时间节点的笔试卷,恰好卡在“传统前端”和“现代前端”的分界线上。今天回过头看,这套题既是对十年前前端知识体系的准确切片,也藏着大量至今依旧有效的底层原理。这篇文章我会把这份试卷的典型题目拆开揉碎,逐题讲清楚考点和答案,再对照当下前端面试的考察维度,聊聊哪些东西十年没变,哪些今年已经是另一套玩法。不管你是准备校招的应届生,还是想评估自己基础是否扎实的社招选手,这篇文章都值得收藏下来慢慢看。

1. 2015年百度前端笔试卷的题型构成与考察思路

先还原一下当年的卷面结构。和大多数2015年前后的互联网大厂笔试一样,百度的这套前端卷没有走“纯选择题”路线,而是主客观结合:单选、多选、填空题、简答题、手写代码题,最后一两道通常是综合性的DOM操作或小功能实现题。现在市面上不少公司的线上笔试已经改成了纯OJ式算法考核,或者用牛客网、赛码这些平台做代码补全,但2015年的做法更接近“白纸手写代码”的原始形态。

从考察面上看,当时的出题思路有几个明显特点:

  • 重基础不重框架:2015年虽然jQuery、AngularJS已经大量用于生产环境,但笔试卷子很少直接考框架API,更多是考原生JavaScript、HTML、CSS功底。面试官默认你会用框架,但更关心你把框架摘掉之后还剩多少底子。
  • 重浏览器机理:事件模型、作用域链、闭包、垃圾回收、渲染机制这些内容反复出现,这和当时的性能优化焦虑有关——移动端Web刚刚爆发,页面卡顿问题突出,前端必须懂浏览器到底怎么干活。
  • 重代码细节:手写代码题通常不要求你写一个完整的应用,而是考函数、继承、去重、排序这些“算法小菜”。但小菜里有大学问,一个细节没注意就会掉进坑里。

放到今天来看,这套卷子其实是一部很好的“前端基础面试题考古样本”。后来的ES6普及、框架框架化、工程化革命,并没有推翻这些底层考察点,反而是在这些地基上盖起了更高的楼。所以如果你现在备战前端面试,与其对着海量的“八股文”焦虑,不如先看看十年前那些经典题目,把地基夯实,再看新东西会轻松得多。

接下来我会按科目拆解这套卷子上的典型真题,每道题都给出参考解答思路,并标注“这道题放到2026年面试还问不问、怎么问”。

2. JavaScript基础题:类型转换、作用域与闭包,十年不变的主战场

2015年的JS题目里,最不缺少的就是“聊JS语言特性”的题目。这类题现在被戏称为“八股”,但不可否认,它依然是前端面试的入场券。

2.1 一道典型的类型转换题目

当年卷子上出现过这样一道题:写出以下代码的输出结果。

var a = 1 + "2" + "3"; var b = 1 + 2 + "3"; var c = "1" + 2 + 3; console.log(a, b, c);

答案是"123" "33" "123"。考点很明确:加法运算里,如果任意一侧是字符串,就会触发字符串拼接,而且是从左到右依次计算,所以1 + "2"先变成"12",再拼"3"得到"123";但1 + 2两侧都是数字,先计算出3,再拼上"3"得到"33"

现在面试往往不是这样直接考了,而是换了个马甲:[] + [][] + {}{} + []各输出什么?这里涉及ToPrimitive内部运算规则:对象先尝试调用valueOf,如果没有返回原始值则继续调用toString。数组的toStringjoin(",")的变种,空数组转成空字符串,因此[] + []结果是""[] + {}结果是"[object Object]";而{} + []在表达式上下文会被解析成一个空代码块加一元正号数组,结果变成了0。这类题目从2010年考到2026年,本质上还是在考“JS的类型系统是不是你想当然的那样”。

2.2 作用域与提升:经典到不能再经典的输出题

2015年百度卷里有一道流传很广的var与函数声明提升题目,类似这样:

var foo = 1; function bar() { if (!foo) { var foo = 10; } console.log(foo); } bar();

答案是10。因为在函数bar的局部作用域内,var foo会提升到函数顶部,初始值为undefined!undefinedtrue,于是进入了if语句块,把foo赋值为10。如果不知道var的提升机制,很容易直观地以为foo取的是全局的1,输出1,然后就被扣分了。

到了2026年,这类题基本被“let/const暂时性死区”的题目替代了,考察方式变成:

console.log(a); let a = 1;

答案是ReferenceError,不能在初始化之前访问。考点从“var如何提升”变成了“块级作用域和TDZ”,但底子仍然是“JS的执行上下文与作用域链”这一套东西。

2.3 闭包:不只是概念,要会写、会改、会防

闭包是2015年前端笔试的“必考大题”,百度卷那年也不例外。一道比较有代表性的手写题是:

实现一个计数器函数,要求每次调用返回值加一,并且支持重置。

参考答案:

function createCounter() { var count = 0; return { increment: function () { return ++count; }, reset: function () { count = 0; } }; } var counter = createCounter(); console.log(counter.increment()); // 1 console.log(counter.increment()); // 2 counter.reset(); console.log(counter.increment()); // 1

考点在于:count变量被返回的两个函数共同捕获,没有一个全局变量被污染,而且外部无法直接拿到count的引用,只能通过暴露的方法读写。这个模式和现代前端里模块化封装的思路一脉相承。

现在面试官大概率不会满足于“你会写闭包”,还会追问:闭包会造成什么副作用?内存泄漏如何产生、如何排查?这里的经典答案是:闭包会让被捕获的变量长期驻留内存,如果创建了大量闭包或闭包链中保留了DOM引用,就可能导致内存泄漏。排查方式是用Chrome DevTools的Memory面板做堆快照对比,观察是否能GC回收。

2.4 this指向:永远的话题

2015年卷子里有一道典型的this指向题,大意是:

var name = "window"; var obj = { name: "obj", getName: function () { return this.name; } }; console.log(obj.getName()); var fn = obj.getName; console.log(fn());

答案是"obj""window"。第二个输出是window的原因在于:fn是在全局环境下被调用的,函数调用位置的调用对象决定了this的值,也就是“谁调用就指向谁”。

如今的考法加入了箭头函数和bind的对比,比如:

var a = { name: "a", fn: () => { console.log(this.name); } }; a.fn();

箭头函数没有自己的this,它沿用了定义时所在上下文(此处就是全局/模块上下文)的this,所以输出为undefinedwindow.name。不管题目外皮怎么换,核心逻辑始终是“this的指向 = 调用位置的调用者,箭头函数则看词法作用域”。

3. 手写代码题:从数组去重到事件委托,那些年的“算法小菜”

2015年百度的笔试卷里,手写代码题占比不低。这类题不像LeetCode那样偏重算法复杂度,而是更偏重“是否符合JS语言的表达习惯”和“代码是否严谨健壮”。

3.1 数组去重:一题多解,从双重循环到Set

数组去重基本上是所有前端笔试题的“开胃菜”,2015年卷子的考法是:不使用ES6,写一个数组去重函数。

常规答案是双重循环:

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

这个写法能过,但效率是O(n²)。稍微进阶一点用对象键值对做哈希,能降到O(n):

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

但这个写法有个坑:hash的键会被转成字符串,数字1和字符串"1"会冲突,所以如果数组中同时有1"1",后出现的会被误删。为了严谨,可以把键设计成typeof item + item

到了2026年,这个问题简写为:

var unique = arr => [...new Set(arr)];

一行结束。但面试官随后就会追问:Set去重是怎么判断两个值相等的?内部用的是SameValueZero算法,和===基本一致,唯一区别是NaN === NaNfalse,但Set认为两个NaN是同一个值。所以你看,去重题的代码量变少了,但底层原理的考察变深了。

3.2 函数防抖与节流:2015年埋下的性能优化种子

2015年百度卷里出现过一道“实现一个scroll事件监听,要求滚动结束后才执行回调”的题。当时很多考生第一反应就是写一个setTimeout包裹一下,然后被追问:如果一直滚动,回调会一直延后吗?正确的解就是防抖。

防抖的现代实现:

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); }; }

为什么用apply(context, args)而不是直接fn()?因为在事件监听场景下,回调函数里的this应该指向绑定事件的那个DOM元素,如果直接调用会把this搞丢。这个细节在当年很多手写答案里被忽略,放在今天依然是考察重点。

节流题也常见,核心思路是记录上次执行的时间戳,间隔达到阈值才执行一次。现在框架里的lodash_.throttle_.debounce已经把这活包圆了,但面试手写依然高频出现——因为这能看出你对异步定时器的理解是否到位。

3.3 事件委托:从绑定到捕获的完整链条

事件委托是2015年前端笔试的常客,典型题目:给一个<ul>下的所有<li>绑定点击事件,点击后高亮当前项。要求用事件委托实现。

参考答案:

document.getElementById("list").addEventListener("click", function (e) { var target = e.target; if (target.tagName === "LI") { // 清除所有高亮 var items = this.querySelectorAll("li"); for (var i = 0; i < items.length; i++) { items[i].className = ""; } target.className = "active"; } });

这道题考察的不只是“click怎么绑定”,还有三个关键点:一是事件冒泡机制,为什么click可以从li冒泡到ul;二是e.targetthis的区别,this是绑定监听的元素,e.target是实际被点击的元素;三是动态添加DOM节点后,新节点不需要重新绑定事件也能被委托捕获。

放到2026年,事件机制依然是高频考点,但问法更新了:React 17把事件从document改绑到root容器上,为什么?这是为了在嵌套React应用时避免多个版本的React同时监听document导致的事件冲突。你看,底层还是事件冒泡与委托的那套原理,只是应用场景换成了框架层。

4. 浏览器与性能优化:HTTP缓存、重绘重排、懒加载那些事

2015年前端笔试的另一大板块是浏览器原理和性能优化。当时的背景是移动Web刚爆发,页面打开慢、交互卡顿是普遍痛点,百度的搜索页、贴吧、知道这些产品对性能要求极高,所以出题人也格外青睐这一块。

4.1 HTTP缓存机制:从Expires到ETag

一道高频题是:说说浏览器HTTP缓存的机制,以及ExpiresCache-ControlLast-ModifiedETag之间的区别。

参考思路:

  • 浏览器第一次请求资源时,服务器返回资源的同时可以带上缓存相关响应头。
  • Expires是HTTP/1.0时代的字段,指定一个绝对过期时间,如Expires: Wed, 21 Oct 2026 07:28:00 GMT。缺点很明显:服务器时间和客户端时间可能不一致。
  • Cache-Control是HTTP/1.1引入的,用max-age=3600这种相对时间方式指定缓存有效期,优先级高于Expires。
  • 当缓存过期后,浏览器会发起条件请求,带上If-Modified-Since(对应服务端的Last-Modified)或If-None-Match(对应服务端的ETag)。如果内容没变,服务器返回304,浏览器继续用本地缓存;如果变了,返回200和新资源。

这个知识点到现在依然是前端面试必问。2026年还会延伸考:强缓存和协商缓存的触发场景、内存缓存和磁盘缓存的区别、为什么ctrl+F5能强制绕过缓存。我在实际性能排查中遇到过很多“改了文件但线上不生效”的案例,十有八九就是缓存策略没配好。

4.2 浏览器的重绘与回流:一题考穿渲染原理

2015年百度卷上有这样一道简答题:什么操作会引起页面的回流(reflow)和重绘(repaint)?如何减少?

核心答法分两块:

  • 回流(现在规范术语叫reflow或layout)是浏览器需要重新计算元素的几何位置和尺寸,比如修改width、height、margin、padding、改变字体大小、窗口缩放、增删DOM节点、查询offsetWidth/scrollTop等属性。
  • 重绘(repaint)是元素的外观变化但不影响布局,比如改colorbackground-colorvisibilitybox-shadow

优化策略:合并多次DOM样式修改(用修改className代替逐条改style)、将元素脱离文档流(使用display: none或绝对定位)后再操作、批量修改DOM时使用DocumentFragmentinnerHTML整段替换、缓存offsetWidth这类强制同步布局的属性值而不是多次读取。

2015年考到这里就结束了,但2026年的面试官会追加一个问题:如果用JavaScript新建一个DOM节点并加到页面上,会不会强制同步刷新样式?答案是:不会,只有当你去读取布局信息如offsetHeight时,浏览器才可能被迫提前执行布局计算来给你一个准确的值。这背后就是渲染器的“增量布局”和“强制同步布局”机制。

4.3 懒加载与图片优化:老题新考

当年有一道场景题:页面有大量图片,如何保证首屏加载速度?常见答法是图片懒加载:

// 伪代码,思路是判断图片是否进入视口 window.addEventListener("scroll", function () { var imgs = document.querySelectorAll("img[data-src]"); for (var i = 0; i < imgs.length; i++) { if (imgs[i].getBoundingClientRect().top < window.innerHeight) { imgs[i].src = imgs[i].getAttribute("data-src"); imgs[i].removeAttribute("data-src"); } } });

这个方案当年能拿分,但现在你再这么答,面试官会接着问:getBoundingClientRect每次滚动都调用,性能好吗?为什么不用IntersectionObserver?于是经典方案变成:

var observer = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { var img = entry.target; img.src = img.dataset.src; observer.unobserve(img); } }); }); document.querySelectorAll("img[data-src]").forEach(function (img) { observer.observe(img); });

再到2026年,loading="lazy"这个HTML原生属性已经成了标准答案,但实际问题里还要考虑屏内首屏图片不要懒加载、SEO场景下懒加载的影响、以及不同浏览器的兼容策略。这十年里,考题的“解法代际”也是一部前端技术演进简史。

5. HTML/CSS考点:布局、选择器与盒模型,依然绕不开的基本功

2015年的卷子里,HTML和CSS题目占比同样不小。今天的前端面试虽然更偏JS和框架,但布局题、CSS细节题依然是筛选人的利器。

5.1 盒模型:一道题能写出三种长度

必考题是描述标准盒模型和IE盒模型的区别。标准盒模型下,box-sizing: content-boxwidth只包含内容区,padding和border另算;IE盒模型下,box-sizing: border-boxwidth包含内容区、padding和border。

当年的进阶题会让考生计算:一个元素width: 200px; padding: 20px; border: 5px;在两种盒模型下实际占用的宽度分别是多少。

  • content-box:实际总宽度 = 200 + 202 + 52 = 250px
  • border-box:实际总宽度 = 200px,内容区宽度 = 200 - 202 - 52 = 150px

这道题至今仍是面试选择题里的常客,只是2015年还要手动计算,现在可以直接口答。

5.2 经典三栏布局:这题从2010年考到2026年

百度2015年的笔试卷中有道布局题:用CSS实现左右宽度固定200px、中间自适应的三栏布局,要求中间部分优先加载。

这个“中间优先加载”是当年比较容易踩坑的地方。因为HTML结构里写在前面的元素会在文档流中先渲染,为了实现中间优先,结构一般写成:

<div class="container"> <div class="main">中间内容</div> <div class="left">左侧</div> <div class="right">右侧</div> </div>

也就是中间内容在DOM结构上排在最前面。再用浮动或定位让它看起来被夹在左右两栏之间。

万金油稳定写法是负margin三栏布局(圣杯布局),核心是左右两栏分别用margin-left: -200pxmargin-right: -200px拉回位置。这套方案兼容性强,在当时被视作标准解。后来Flexbox普及后,三栏布局变成:

.container { display: flex; } .main { flex: 1; } .left { flex: 0 0 200px; order: 1; } .right { flex: 0 0 200px; }

现在面试官已经很少让人手写圣杯布局了,而是直接问Grid和Flexbox各自的适用场景。但2026年的团队协作里,你会发现很多老项目的CSS代码还留着十年前的圣杯布局写法。懂这些老语法不是怀旧,而是接手老项目的基本生存技能。

5.3 CSS优先级与选择器细节

选择题里常考的样式优先级计算,放在现在仍然适用。一条通用规则是:

!important> 内联样式 > ID选择器 > 类选择器/属性选择器/伪类 > 元素选择器/伪元素

那年有一道辨析题:ul li aa.nav-link哪个优先级高?前者是三个元素选择器,特异性值是0-0-0-3;后者是一个类加一个元素,特异性值是0-0-1-1。所以后者高。这类题在今天的前端面试里依然会出现,而且有了新变体::is():where()的优先级怎么计算。:where()的特异性恒为0,:is()会取参数列表里面最高的特异性。CSS选择器这一题,从2015年到2026年,考法从“会不会算”变成了“知不知道新伪类函数的特殊性规则”。

6. 框架与工程化的演进:2015年的jQuery时代 vs 2026年的组件化时代

2015年百度笔试极少直接考察框架源码或工程化配置,但问到了事件绑定的封装、组件化设计思想、模块化加载这类偏工程的问题。如果当时考的是“jQuery的onbind有什么区别”,今天的笔试卷大概率问的是“Vue3的响应式原理和React的Hooks执行顺序”——考察对象变了,考察内核没变:你到底理不理解你每天都在用的工具,而不是只会按文档写。

6.1 当年这题:事件绑定方式的区别

jQuery题目常见形式是:$(selector).click(fn)$(selector).bind("click", fn)$(selector).on("click", selector, fn)有什么区别?

核心要点:.click().on()的简化写法;.bind()直接给当前已存在的元素绑定事件,无法处理动态添加的元素;.on()可以传一个选择器参数实现事件委托,动态元素不用额外绑定。考察的底层就是事件委托原理。

现在React里类似的题目变成了:绑定事件时用箭头函数和不用箭头函数有什么区别?用箭头函数会丢失this吗?在render里绑箭头函数每次渲染都会创建新函数,会不会引起不必要的子组件重渲染?这些问题的影子,依然能从2015年的jQuery题里找到源头。

6.2 组件化思想:从“封装一段代码”到“框架的组件生命周期”

2015年的简答题有类似“列举前端组件化的方案”的说法。那时候的主流答案:Backbone的View、AngularJS的Directive、自己写的JS封装类。把一段HTML结构、样式、行为封装成一个可复用的单元,是大厂效率的核心诉求。

2026年考组件化,考得更深:Vue2的Options API与Vue3的Composition API优缺点、React函数组件和类组件的生命周期对应关系、组件通信方式(props、事件、ref、provide/inject、store、Context)、受控组件与非受控组件的区别。表面上看是完全不同的知识体系,底层却是同一道题——“组件之间如何隔离状态、如何通信、如何复用逻辑”。如果你在2015年打下了牢固的模块化思想基础,学这些新框架反而非常顺畅。

6.3 工程化:从grunt/gulp到Vite,问题变成了“你还会不会配”

百度2015年笔试有一道加分题:谈谈你对前端构建工具的了解。当时的主流答案是grunt、gulp、fis,再加上刚冒头的webpack。webpack那句经典的“一切皆模块”让不少考生眼前一亮,因为那时模块化加载方案还是AMD、CMD、CommonJS各说各话。

现在的工程化面试题已经从“会不会写gulpfile”变成“webpack的构建流程中loader和plugin分别做什么”“Vite为什么开发环境快、生产环境要rollup打包”“Tree Shaking的工作原理是什么”。我在带新人时发现一个现象:很多同学会用Vite但讲不清它为什么比webpack快,就像2015年很多人会用gulp但讲不清文件流是怎么流起来的。这个“会用但不懂原理”的问题,从十年前一直延续到今天。所以我觉得,不管时代怎么变,“工具原理”这个考察维度是永远不会消失的。

7. 写在最后:2015年的题放到2026年,还有多少参考价值

说句实在话,如果你现在拿着2015年百度这套笔试卷去裸考2026年的前端岗位,大概率过不了初筛——因为考察范围和深度已经完全不是一个量级。各种框架源码、工程化配置、性能优化策略、TypeScript类型体操、微前端方案、Node/全栈能力,都是2015年没影的东西,而且现在每一块都能单独拉出来考一整轮。

但这不等于老题没价值。我自己的观察是,前端面试最核心的那部分“底层内功”——JavaScript语言特性、浏览器工作原理、网络与缓存、前端安全、经典布局方案、事件机制、闭包与作用域——2015年在考,2026年依然在考,只是换了皮。很多新框架、新工具、新范式,本质上都是对这些底层能力的不同封装和组合。

所以我给正在准备前端面试的朋友一个建议:手里那些“老八股”题目别急着全扔,把它们当成地基去看,先把地基补扎实,再往上堆框架和工程化知识。如果你有技术博客或者知识库,建议按“语言基础、浏览器原理、网络协议、框架原理、工程化、性能优化”这几个方向分类整理,把老题和新题放在一起对照着看。你会发现面试题根本不神秘,它只是技术演进在某个时间节点的切片而已。这一点,我在看2015年这份百度卷的时候体会尤深。

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

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

立即咨询