2016年那会儿,前端圈子其实处于一个很有意思的过渡期。jQuery还是很多项目的默认配置,Bootstrap做后台页面司空见惯,AngularJS和React已经开始有人讨论,但大多数校招笔试的考法跟今天差别并没有想象中那么大。用友那年校招的web前端笔试题,我印象最深的是它“不过分追新”的风格:考的全是JS语言本身、浏览器原理、网络基础这些硬功夫,反而比后来那些追框架、追工具链的题目更值得复盘。
这篇文章我想借这套题,把前端笔试背后的考察逻辑拆开讲透。无论你是正准备校招的应届生,还是已经工作几年想回头补基础的前端开发,都能从中找到一些“当时没想明白”的答案。笔试题这东西,真正的价值从来不是背答案,而是通过它理解面试官到底在筛选什么样的人。
1. 题目整体设计与意图拆解
1.1 当年校招笔试的题型分布
根据当时参加过笔试的同学反馈和我对这类经典题型的了解,用友2016校招前端笔试题型大致可以分成四类:单选题、多选题、程序结果分析题、手写代码题。没有后来常见的问答题和开放设计题,整体更偏向“基础知识的广度 + 代码能力的底线”。
| 题型 | 考察方向 | 典型内容 | 建议用时 |
|---|---|---|---|
| 单选 | JS基础、HTML/CSS、网络 | 数据类型、事件、HTTP状态码 | 15分钟 |
| 多选 | 容易混淆的知识点 | 闭包、作用域、==与===、数组方法 | 15分钟 |
| 程序结果分析 | 代码阅读与运行逻辑 | 变量提升、this指向、异步执行顺序 | 25分钟 |
| 手写代码 | 基本功与算法思维 | 数组去重、字符串处理、简单排序 | 35分钟 |
这个结构放在今天看依然合理。笔试时间一般控制在90分钟,题量在30道左右。它的目的不是让你“全做完”,而是通过有限的时间判断你对基础知识掌握的熟练度。很多人死在“前面选择耗时太多,后面大题没时间写”,这本身就是一种筛选——看你有没有大局观。
1.2 设计意图:校招笔试到底想筛什么
校招和社招最大的区别在于:应届生基本没有真实项目经验,面试官能考察的只有基础理论、逻辑思维和学习潜力。所以笔试的设计逻辑其实很朴素——基础题摸广度,编程题摸深度,分析题摸思维习惯。
用友这类To B软件公司,前端岗位要做的是企业级管理系统、大屏展示、流程引擎这类偏复杂的后台应用。这些项目对基础稳定性要求极高,一个闭包用错就能导致整个报表页面崩溃。所以它们笔试题几乎不考框架,因为jQuery和Backbone这类框架可以入职后一个月学会,但原型链、事件循环、作用域这种东西学不会就永远写不出可靠的代码。
提示:如果你现在准备校招,看到笔试题里全是基础题,不要觉得“太简单”或者“太老”。基础题不是送分题,而是高压线上的探针,每个选项都在试探你理解得够不够深。
2. 核心考点解析与答题思路
2.1 JavaScript语言基础:四个必考重灾区
我当时帮人复盘这份笔试题时,发现JS基础部分的考点高度集中,几乎可以用“四个必考”来总结:数据类型与类型转换、闭包与作用域、this指向、原型链与继承。这四个点占到基础部分的70%以上。
数据类型这关,最大的坑是“看似简单实则混淆”。typeof null返回object、[] == false返回true、'1' + 2得到字符串'12',这些都是经典陷阱。更隐蔽的是数组方法在隐式转换时的行为——[1,2] + [3,4]得到'1,23,4',因为数组先调toString()变成字符串再拼接。这类题看起来是“脑筋急转弯”,实际上考察的是你对隐式转换规则的掌握是死记硬背还是真正理解。
闭包与作用域的题,常见形式就是“下面代码输出什么”。比如:
for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 100); }这道题考的是var没有块级作用域、setTimeout回调在循环结束后才执行、闭包捕获的是变量引用而不是值。答案不是0,1,2,3,4,而是5,5,5,5,5。当年很多人栽在这里。改成let之后输出0,1,2,3,4的差异,以及用立即执行函数闭包保存i的模式,都属于标准解法。这个知识点直到现在依然是面试高频题,因为异步加闭包是前端代码出bug的重灾区。
this指向的题,本质上考的是“调用方式决定this”这条铁律。普通函数调用、对象方法调用、构造函数调用、call/apply/bind调用,四种场景下this各不相同。当年经典题是这样:
var name = 'window'; var obj = { name: 'obj', getName: function() { return function() { return this.name; }; } }; console.log(obj.getName()()); // "window"因为obj.getName()执行后返回了一个普通函数,调用时没有调用者上下文,所以this指向window。要拿到obj就得在闭包里保存this,也就是var self = this。这个问题在回调函数、事件监听、定时器里非常常见,值得反复练习。
原型链考的是“构造函数、实例、原型对象三者的关系”。instanceof判断的是原型链上是否存在对应原型,而hasOwnProperty只判断自身属性。继承的典型手写题是Object.create的实现,或者用寄生组合式继承写一个Animal和Dog的继承关系。这类题不要求你写出完美代码,但要体现出你对“属性查找沿原型链逐级向上”这一机制的认识。
2.2 浏览器与网络基础:为什么前端必考这个
前端笔试考浏览器和网络,在2016年并不算“超纲”。用友的题里出现过的几个方向包括:事件冒泡与捕获、页面加载流程(DOMContentLoaded和load的区别)、HTTP状态码语义、跨域问题的常规解决方案。
事件模型属于“必须理解到位”的内容。事件冒泡是从目标元素向上传播到document,事件捕获是从document向下传播到目标元素。addEventListener的第三个参数传true表示捕获阶段触发,不传或传false表示冒泡阶段触发。考得深一点的会问“事件委托的原理”——利用冒泡将子元素的事件统一交给父元素处理,优点是可以处理动态添加的节点以及减少事件绑定数量。这个知识点在今天的React/Vue项目中依然有用,事件委托本质上没有过时。
HTTP状态码也是选择题常客。304表示“使用缓存资源”,403是“禁止访问”,404是“资源不存在”,500是“服务器内部错误”。笔试一般考到这种程度就够了。但如果你能在答题时额外说明“301和302的区别是永久重定向和临时重定向,这会影响浏览器对缓存的处理”,会显得你确实懂网络,而不是背过状态码。
跨域问题是2016年面试的“必答大项”,笔试里一般不会让你写完整方案,更常见的是以简答形式问“列举解决跨域的方式并说明原理”。常规答案包括JSONP(动态script标签不受同源策略限制,利用回调函数接收数据)、CORS(服务端设置Access-Control-Allow-Origin响应头)、代理转发(同源服务器做中间层转发请求)。当时Nginx代理用得很多,devServer.proxy这种原理倒是后来才普及的。这类题不用写太多,但要思路清晰、顺序合理。
2.3 手写代码题:算法不难,难在工程习惯
手写代码题一般在两道左右,一道数组相关,一道或者两道是字符串和逻辑类。典型题目有:数组去重、数组扁平化、实现防抖节流、字符串反转、找出字符串中出现次数最多的字符、手写类型判断函数。
这类题考察算法程度其实很浅,难度大概在LeetCode easy级别。但面试官真正想看的不是算法效率,而是——变量命名规不规范、有没有考虑边界情况(空数组、空字符串、null传入时会不会报错)、代码逻辑是否容易读懂。这些才是企业级开发中最重要的能力。
注意:笔试手写代码,不要为了追求“一行代码搞定”而牺牲可读性。
Array.from(new Set(arr))确实能数组去重,但如果你能同时写出reduce循环版本并解释差异,面试官会更认可。多解法体现了维度不同的思考。
3. 这套笔试背后的能力模型图谱
3.1 从笔试题看2016年的前端技术生态
2016年对前端行业来说是一个很特殊的年份。当年jQuery依然是绝大多数公司项目的基石,$.ajax和$(document).ready几乎是每个前端日常都会写的代码。AngularJS 1.x在企业级应用里有一席之地,React刚刚起步,Vue 2.0在这一年发布,Webpack正在取代Grunt和Gulp成为主流打包工具。
用友的笔试题没有直接考这些框架,但它考的所有基础知识点,都是这些框架底层运行的基石。jQuery的$(selector).on('click', fn)本质上就是事件委托的封装。Vue 2.0的响应式原理离不开Object.defineProperty,而这个API本身就在考察你对“对象属性描述符”的理解。React的合成事件系统也要依赖冒泡机制。这说明一份有质量的笔试题,考的不是“技术潮流”,而是“技术底部”。
现在回头看,我当时觉得“这套题真老”的想法是错的。后来我面试候选人时发现,很多简历上写着React/Vue项目经验的人,问他冒泡和捕获都说不清楚,问他setTimeout和Promise的执行顺序也支支吾吾。框架可以快速学,基础是慢功夫。招聘方用笔试筛基础,就是这个原因。
3.2 分层筛选逻辑:基础合格,思维加分
一份好的笔试题,除了判断“会不会”,还能判断“好不好”。同样是数组去重,思路不同反映的水平完全不同。
| 提交代码 | 问题 | 能力判断 |
|---|---|---|
for循环 +indexOf | 能跑,但复杂度O(n^2),且indexOf对NaN无效 | 至少会写代码 |
filter+indexOf | 代码简洁,但依然O(n^2),ES5时代的常规操作 | 见过一些写法 |
new Set() | 代码最短,但不懂Set的兼容性 | 了解ES6但不深 |
reduce+ Map | 复杂度O(n),思路完整,兼容性好 | 有算法意识 |
| 同时写出去重后再排序并说明稳定性 | 考虑全面,表达清晰 | 工程思维较强 |
面试官不只看能不能通过,还看你的解题路径。如果只写new Set(arr),说明你只是“听说过ES6”。如果写reduce+ 对象存储,并说明typeof区分'1'和1的问题,说明你真的处理过数据。
这套分层逻辑在校招笔试里体现得尤其明显——大家都没有项目经验,区分度主要靠“基础扎实程度”和“思维细腻程度”。这也解释了为什么同一道题,不同人得分可能天差地别。
4. 经典题的实战模拟与参考实现
4.1 基础概念题速测:你能答对几道
这里还原一份我整理的经典基础题速测,不严格对应某一年的原题,但覆盖方向一致。你可以先自己做一遍,再对照后面的解析。
typeof undefined === typeof null,这句判断的结果是什么?"5" - 1和"5" + 1分别输出什么?console.log(1 + 2 + "3")输出什么?var a = {n: 1}; var b = a; a.x = a = {n: 2}; console.log(a.x) 和 console.log(b.x)的结果?- 如何判断一个变量是否为数组?
null == undefined和null === undefined的结果是什么?setTimeout的this在非严格模式下指向什么?['1', '2', '3'].map(parseInt)的结果是什么?
逐题说一下。第1题,typeof undefined是"undefined",typeof null是"object",字符串比较,结果是false。这道题错的人很多,因为null == undefined是true,但typeof的结果是字符串,比较的是字符串字面量。
第2题,"5" - 1得到数字4,减法触发隐式转换将字符串转数字;"5" + 1得到字符串"51",因为加号有一端是字符串时执行拼接。第3题从左到右计算,先1 + 2得3,再3 + "3"得"33"。
第4题是一道经典的连续赋值题。a.x = a = {n: 2}中,a.x引用先确定(指向原对象的x属性),然后a被重新赋值,最后将新对象赋给原对象的x属性。结果是a.x为undefined,而b.x指向新对象。这题考察引用赋值和赋值顺序,少见但能有效拉开区分度。
第5题,判断数组用Array.isArray(arr),在ES5之前也有Object.prototype.toString.call(arr) === '[object Array]'的通用方法。arr instanceof Array在多iframe环境下不可靠,因为不同全局对象各有各的Array构造函数。我记得当时有同学写typeof arr === 'object',这只能判断它是引用类型,不能判断它是数组。
第6题,null == undefined为true(宽松相等认为它们相等),null === undefined为false(类型不同,null是object,undefined是undefined)。第7题,setTimeout中的回调函数,非严格模式下this指向window(或全局对象),严格模式下是undefined。第8题,parseInt作为map的回调函数会被传入三个参数(当前值、索引、数组本身),实际上是parseInt('1', 0)、parseInt('2', 1)、parseInt('3', 2),结果是[1, NaN, NaN]。这题在2016年属于“见过才不亏”的冷门考点,但到今天已经变成常规面试题。
4.2 手写编程题的完整思考过程
以一道经典编程题为例:实现一个函数,将数组扁平化,并去重,最后升序排列。例如输入[1, [2, 3], [4, [5, 6]], 1, 2],输出[1, 2, 3, 4, 5, 6]。
我的建议答题顺序是:先写一个能跑的版本,再优化。
第一步,数组扁平化。递归是最直观的思路:
function flatten(arr) { var result = []; for (var i = 0; i < arr.length; i++) { if (Array.isArray(arr[i])) { result = result.concat(flatten(arr[i])); } else { result.push(arr[i]); } } return result; }这个版本的优点是兼容性好,ES5环境下也能运行。缺点是递归处理,如果嵌套层级极深可能有栈溢出风险。它对应“面试官问你这个方案的问题在哪里”时,你要能说出来。
第二步,去重。如果允许ES6,Array.from(new Set(flattened))最短。如果考虑兼容性,可以用reduce加对象存储:
function unique(arr) { var map = {}; return arr.filter(function(item) { if (map[item]) { return false; } map[item] = true; return true; }); }注意这里map[item]用对象的key做去重,会把数字1和字符串'1'混在一起,实践中有隐患。更严谨的写法是map[typeof item + item]。这一步能体现你比别人多想的细节。
第三步,排序。用Array.prototype.sort默认按字符串排序,数字升序必须传比较函数function(a, b) { return a - b; }。
完整版:
function flattenUniqueSort(arr) { var flat = flatten(arr); var uniq = unique(flat); return uniq.sort(function(a, b) { return a - b; }); }这道题三合一,综合考察递归思维、类型判断、数组方法和边界处理。踩坑点集中在:忘记数组扁平化后还是引用类型、去重时对象key的类型合并、排序时默认字典序问题。如果你能把每一步为什么这么写的理由说清楚,面试官会觉得你不仅是“背了题”,而是理解了。
4.3 开放题怎么答才有亮点
2016年用友的笔试题虽然没有开放设计题,但当时的面试环节会有类似“如果要你优化一个页面的加载速度,你的思路是什么”这样的追问。笔试里如果附带简答题,也常见“说明AJAX请求的完整过程”“你怎么理解前端模块化”这类开放性题目。
开放题的答题策略,不是追求“唯一标准答案”,而是展示分析框架。以“优化页面加载速度”为例,一个好的回答应该按层级展开:
- 资源层面:压缩JS/CSS文件、合并请求、使用CDN、开启gzip;
- 渲染层面:CSS放头部、JS放底部或加
defer/async、减少DOM层级; - 缓存层面:合理设置
Cache-Control和ETag,利用浏览器缓存; - 数据层面:接口按需加载、首屏只请求关键数据、图片懒加载。
只回答“压缩JS/CSS”是不够的,因为这属于最基础的背答案。如果你能进一步说明“CSS阻塞渲染、JS阻塞解析,所以CSS要尽快到达浏览器,JS可以延迟执行”,就体现出你真的理解浏览器工作方式。这个层次感在开放题里非常加分。
5. 常见问题与避坑经验
5.1 这道题为什么会做错:典型错误盘点
我帮人改笔试题的时候积累了一大堆“一眼就能看出问题”的错误。最普遍的是分号问题和变量命名问题,手写代码时经常有同学不写分号、变量名全是a、b、c。这不是致命错误,但在团队协作里会显得不职业。笔试阅卷人对“看起来像不像能直接部署的代码”非常敏感。
逻辑层面的错误更多集中在类型比较上。==和===乱用是重灾区。写数组去重用result.indexOf(item) === -1判断存在性,结果遇到NaN就失灵。写字符串处理不考虑大小写和空格,导致函数一遇到真实数据就崩。这些不算“不会做题”,而是“缺少工程意识”,而工程意识恰恰是企业最看重的。
时间分配错误也很可惜。基础题卡壳太久,导致后面高分的编程题只能草草收尾。我见过不少同学小题错2道、大题全空的卷子。正确的策略是:基础题每题最多2分钟,超过就先跳过;代码题至少留30分钟。
5.2 复盘这套题:哪些经验到现在还能用
距离2016年已经过去很多年,我看着这份笔试题最大的感受是:基础知识的考察方式在变,但考察对象没变。当年考闭包输出,现在依然考闭包输出;当年考数组去重,现在最多加一个“写出三种实现并说明复杂度”;当年考事件循环,现在考Promise、async/await的执行顺序。核心,永远是“你对这门语言理解得有多深”。
对于准备校招的读者,我的建议是不要只刷题库。把ES5中数组相关方法(map、filter、reduce、forEach、sort)的底层行为和常见坑过一遍,把闭包、this、原型链、事件循环这四座大山翻来覆去理解透,比刷100道“押题”管用得多。笔试的题目可以换,但内核不会变。
提示:面试前找一套两三年前的笔试题练手,不是让你背答案,而是让你感受出题人的视角。你在做2020年以后的题目时,可能已经被框架淹没;而看老题会让你重新意识到,JS这门语言本身才是最值得花时间的。
结尾:关于基础,我的真实体会
工作多年之后回头看2016年这份“老掉牙”的笔试题,我反而觉得它出得相当扎实。后来我面试过很多候选人,真正让我留下深刻印象的,不是谁简历上写了多少个框架,而是谁能在白板上把this指向讲清楚、能把闭包的实际用途说出个一二三。技术栈每两三年就换一轮,但基本功是十年不变的东西。如果你还在准备校招,建议认真做一遍类似的老题,并且别只看答案,多问问自己“为什么”;如果你已经工作,偶尔翻翻这类基础题,也能帮你从日常的业务逻辑里跳出来,重新审视那些很久没再细想的JS原理。这是我个人觉得最值得保留的学习习惯。