如果经历过那几年的校招季,大概率会对这类题目有印象:前端笔试不是让你默写 API,而是把 JavaScript 的语言特性、浏览器渲染机制、网络通信原理混在一张卷子里,考的东西看起来都是基础,但每一个选项都在挖坑。“用友 2017 校招 web 前端笔试题(二)”就是这类题型的典型代表——题目难度不算夸张,可覆盖范围很广,能比较真实地反映一个候选人对前端底层知识的掌握程度。
这篇文章我会把这套题涉及的核心知识点完整拆开:从题型分布到解题思路,从 JavaScript 的基础陷阱到浏览器渲染优化的经典场景,每一步我都会给出为什么这么考、怎么答才稳的解释。无论你是正在准备校招的应届生,还是想系统性查漏补缺的初中级前端工程师,这套题都值得认真过一遍。
1. 整体题型分布与出题逻辑
1.1 传统软件企业前端笔试题型概览
用友这类深耕企业级软件的公司,校招笔试的命题风格和互联网公司有明显差别。互联网大厂喜欢考算法、考 LeetCode 风格的编程题,而用友的笔试题更偏向工程实用性和语言基础的结合,HTML、CSS、JavaScript 三件套都有涉及,还会夹杂少量浏览器原理和前端框架相关问题。
从“(二)”这个编号能看出,这是一个系列试卷,大概率分为单选、多选、填空、简答、编程五个板块。选择题主要考察概念理解和细节记忆,比如变量提升、事件冒泡、闭包引用等;填空和简答则侧重考察知识点的精确表述;最后的编程题通常围绕数组去重、防抖节流、原型继承这些经典场景展开。
这种命题风格的背后其实反映了企业用人的真实需求:用友的核心产品是企业级管理系统,前端页面大量涉及表格、表单、复杂交互和数据处理,不像 C 端产品那样追求炫酷的视觉动效,但要求工程师对 JavaScript 的语言特性有扎实掌握,能写出稳定、可维护、兼容性好的代码。所以笔试题目多为基础知识的深入考察,而不是偏题怪题。
1.2 2017 年校招笔试题的主要考核重点
站在当下回看 2017 年的前端笔试题,会发现几个非常明显的高频考点。第一是 JavaScript 作用域与闭包,几乎每份前端笔试都会考,用友这份也不例外,涉及 var 变量提升、块级作用域、闭包引用循环变量导致的经典问题等。
第二是 ES6 新特性。2017 年正是 ES6 全面普及的阶段,let/const、箭头函数、解构赋值、模板字符串等成为笔试题的常客,尤其是 let 和 var 的区别,几乎隔几道题就会出现一次。
第三是浏览器渲染机制和性能优化,题目会涉及 DOM 重排重绘、CSS 样式计算、资源加载顺序等。这一点也侧面反映了企业对企业级应用页面性能的重视——ERP 系统的页面往往包含大量数据表格和复杂表单,渲染性能直接影响用户体验。
第四是跨域和网络请求。AJAX 同源策略、JSONP、CORS 这些知识点在企业级开发中是刚需,因为前后端分离的架构模式在 2017 年已经比较普及。
2. JavaScript 基础重点解析
2.1 作用域、闭包与变量提升问题
这套笔试题里,作用域相关的题目是最能拉开差距的部分。比如一道很经典的题目:
var a = 1; function test() { console.log(a); var a = 2; } test();问输出是什么。很多候选人会脱口而出“1”,但实际上输出是undefined。原因在于变量提升:var a的声明会被提升到函数作用域的顶部,但赋值a = 2保持在原位置,所以实际执行顺序是:
function test() { var a; console.log(a); a = 2; }所以答案是undefined而不是报错,也不是1。理解了变量提升机制,这类题就不会丢分。需要注意的一点是:ES6 的let和const也有声明提升,但存在暂时性死区,在声明位置之前访问变量会直接抛出ReferenceError,这是 var 和 let 最关键的行为差异。
闭包是另一类必考题,典型场景是循环中给元素绑定事件:
for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 100); }输入结果是连续五个 5,而不是 0、1、2、3、4。原因是setTimeout的回调函数在循环结束后才执行,此时i已经变成 5,循环中的每个闭包引用的都是同一个i变量。解决方案有两个,一个是使用 ES6 的let声明循环变量,每次迭代都会创建独立的绑定;另一个是使用立即执行函数表达式传参创建独立作用域:
for (var i = 0; i < 5; i++) { (function(j) { setTimeout(function() { console.log(j); }, 100); })(i); }这道题在笔试中出现的频率极高,我建议不要只记答案,而是要把“闭包捕获的是变量的引用,而不是变量的值”这句话刻在脑子里。只有理解了这一点,无论题目怎么变形,比如把var换成let、把console.log换成 DOM 操作,你都能准确判断结果。
2.2 this 指向与原型链问题
this 指向问题在 JavaScript 笔试中同样是重头戏。用友这套题里有一个很典型的题目:
var name = 'window'; var obj = { name: 'object', getName: function() { return this.name; } }; var fn = obj.getName; console.log(fn()); console.log(obj.getName());第一行输出window,第二行输出object。原因很好理解:fn被赋值后,调用时它的this取决于调用位置,由于是在全局环境中调用,this指向window对象;而obj.getName()是通过对象方法调用的形式执行,this指向obj本身。
这里面有个容易混淆的地方:函数定义在哪个对象里并不重要,重要的是调用时用什么方式调用。我见过不少候选人把“定义时的位置”当作 this 的指向依据,这在笔试中会连续错好几道题。更进阶的考法是结合call/apply/bind:
var obj2 = { name: 'obj2' }; console.log(obj.getName.call(obj2));输出结果是obj2,因为call显式指定了this指向为obj2。这类题目考察的是候选人对 this 动态绑定的理解深度,答案并不难,但需要把调用方式、显式绑定、构造函数绑定这几种情况梳理清楚。
原型链方面,笔试常考的是instanceof的判断逻辑和constructor的指向问题。比如:
function Person(name) { this.name = name; } var p = new Person('tom'); console.log(p instanceof Person); console.log(p instanceof Object); console.log(p.constructor === Person);三个结果都是true。原型链的查找过程和作用域链类似,先在对象自身属性中查找,找不到就去__proto__指向的原型对象上继续找,直到Object.prototype,再往上就返回 null。理解了这个链路,instanceof的原理就自然掌握了:它本质上是判断构造函数的prototype对象是否出现在实例的原型链上。
3. ES5/ES6 语法与数组新特性
3.1 高频语法考点的对比与判断
2017 年的笔试题正处于 ES5 和 ES6 交替的时期,所以题目经常把两种语法放在一起对比,让候选人判断区别。最常考的三个对比是var与let/const、普通函数与箭头函数、传统字符串拼接与模板字符串。
我见过一道题目是这样的:给出用var和用let分别声明的循环变量,在for循环内部异步回调中读取该变量,写出输出序列并解释原因。这道题把变量作用域、闭包、异步执行三个知识点全部串在一起,综合性很强,但拆开看就是前面提到的闭包陷阱问题。
箭头函数与普通函数的对比也是一个重点。箭头函数的特殊之处在于没有自己的this,它继承的是定义位置所在作用域的this。笔试中经常这么考:
var name = 'window'; var obj = { name: 'object', getName: () => { return this.name; } }; console.log(obj.getName());答案是window,因为箭头函数定义在obj字面量内部,但它的作用域链向上找,this是全局的window。这个结果和普通函数完全相反,出题人就是故意考这个反直觉的点。判断方法很简单:箭头函数的this在定义时就确定了,普通函数在调用时才确定。
模板字符串在 2017 年已经不算新东西,笔试中主要考的是基本用法和嵌套表达式的能力,只要知道用反引号包裹字符串、用${}嵌入表达式即可,不在话下。
3.2 一道典型手写题:数组去重
数组去重几乎是前端校招笔试题的“保留曲目”,用友这份试卷里也出现了。2017 年的考点主要在indexOf遍历和 ES6 的Set,但出题人会限制条件,比如“不用 Set 实现”或者“考虑对象元素的去重”。
基础版本的数组去重很简单:
// 方法一:使用 Set var uniqueArr = [...new Set([1, 2, 2, 3, 4, 4])]; // 方法二:使用 filter + indexOf var arr = [1, 2, 2, 3, 4, 4]; var uniqueArr = arr.filter(function(item, index) { return arr.indexOf(item) === index; });filter + indexOf的思路很巧妙:indexOf返回元素第一次出现的位置,如果当前索引和第一次出现的位置相同,说明这是第一次遇到的元素,保留下来;否则说明前面已经有重复元素了。这个写法简洁,又考察了对数组方法的熟悉程度。
进阶的考法是要求保持原数组顺序去重,元素中包含对象,对每个对象按照id字段去重。这时就不能简单用Set了,需要引入一个空对象作为哈希表:
var arr = [{id: 1, name: 'a'}, {id: 2, name: 'b'}, {id: 1, name: 'c'}]; var map = {}; var result = arr.filter(function(item) { if (map[item.id]) { return false; } map[item.id] = true; return true; });这道题的坑点在于:直接用Set对对象去重是无效的,因为每个对象字面量都是不同的引用,Set的相等性判断基于引用地址。所以必须显式指定去重字段。我在实际面试中见过有候选人用嵌套循环判断每个字段是否相等,也能实现,但复杂度是 O(n²),写成哈希表方案会显得更有经验。
4. 浏览器渲染机制与性能优化
4.1 页面渲染流程与重排重绘
浏览器相关的题目在笔试中一般以简答题形式出现,考察候选人对页面从输入 URL 到显示完整内容的整个过程中,每一步发生了什么的掌握程度。完整的链路大致是:DNS 解析、TCP 连接、HTTP 请求、服务器响应、HTML 解析、CSS 解析、DOM 树构建、渲染树构建、布局计算、绘制和合成。
前端岗位最关注的是 DOM 树构建到绘制这后半段。HTML 解析过程中遇到<script>标签会暂停 DOM 解析,下载并执行完脚本后才继续解析,这就是为什么我们习惯把 JS 放在body底部,或者给脚本加defer和async属性。这背后的原因是 JS 可能会通过document.write()修改文档结构,如果在解析过程中执行脚本,解析器无法确保后续内容不受影响。
重排和重绘是另两个高频概念。重排也叫回流,指页面部分或整体重新计算布局,代价很高;重绘是元素样式变化但不影响布局时的重新绘制,代价相对较低。笔试题目通常会给出几种操作,让候选人对号入座。典型的操作包括:改变元素宽高、增删 DOM 节点、修改font-size,这些属于重排也会导致重绘;单纯修改color、background-color、visibility,这些只触发重绘。
有一个容易忽略的点:读取某些布局属性比如offsetWidth、getBoundingClientRect()时,浏览器为了返回准确的值,会被迫提前刷新渲染队列,把此前累积的样式变更立即同步计算一遍。如果一个循环里反复写样式再读布局属性,就会导致布局抖动,性能急剧下降。笔试和面试中如果问“如何减少重排重绘”,核心思路就是减少对布局属性的频繁读取、使用class批量修改样式、用DocumentFragment批量操作 DOM、或者将动画元素设为position: fixed/absolute脱离文档流。
4.2 前端性能优化的经典切入点
企业级应用对前端性能的敏感度非常高。一个 ERP 的列表页如果渲染上千行数据,直接操作 DOM 会产生明显的卡顿。所以笔试中常有一道简答题:如果页面加载缓慢,你会从哪些方面排查和优化。
考察的答案通常分为三个层面:资源加载层面、渲染执行层面和代码层面。资源加载层面包括静态资源使用 CDN、启用 Gzip 压缩、压缩图片与合并 CSS/JS 文件、设置合理的浏览器缓存策略;渲染执行层面包括将<link>放在head中尽快加载样式、将<script>放在body底部或使用defer避免阻塞解析、关键渲染路径上的资源内联;代码层面包括减少 DOM 操作次数、避免频繁重排重绘、使用事件委托减少事件监听器的数量。
我在实际面试中通常会追问一句:如果表格数据量非常大,怎么优化渲染?这个问题有几个层次。基础回答是懒加载或分页加载,只渲染当前可视区的内容;层次高一些的回答是虚拟滚动,只渲染可视区域加缓冲区内的行节点,配合绝对定位模拟滚动条高度;如果面试者能说出虚拟滚动的具体实现思路,比如计算scrollTop、起始索引、渲染条数和偏移量,就能在技术面上加分不少。
5. 框架、跨域与其他常见题目
5.1 框架原理考察的深度
2017 年的前端圈已经基本是 Vue 和 React 的天下,用友笔试中也出现了框架相关题目。但笔试题不会直接考“Vue 源码里某个函数怎么实现”,因为校招生实战经验有限,更多是考框架核心思想和基础 API 的使用。
Vue 相关的题目集中在数据双向绑定的原理上,考察候选人是否知道Object.defineProperty在其中的作用,以及getter依赖收集和setter派发更新的过程。有一道常见的简答题:Vue 实现数据双向绑定的核心思想是什么,简单描述v-model的实现原理。答案要点是:Vue 在数据对象的属性上通过Object.defineProperty定义getter和setter,当数据变化时触发setter,通知依赖更新对应的 DOM;v-model本质上是:value绑定加@input事件监听的语法糖,在表单控件上绑定数据并把用户输入实时写回数据。
React 相关的题目则侧重单向数据流和虚拟 DOM。虚拟 DOM 的核心价值不是“比直接操作 DOM 快”,而是提供了一种声明式的 UI 开发方式:开发者描述界面在某个状态下的样子,React 负责通过 diff 算法找出差异并最小化更新真实 DOM。这个理解也是面试官很希望听到的,因为它说明候选人不只是套用框架,而是理解了框架设计背后的权衡。
5.2 经典边界问题与手写实现
除了框架,笔试的编程题通常会有一道手写题,让候选人在纸上或编辑器里实现某个小功能。除了数组去重,比较常见的还有防抖、节流、深拷贝、Promise 简单实现和函数柯里化。用友这套题中有一道是手写防抖函数。
防抖的核心思想是:事件触发后延迟执行,如果在延迟时间内再次触发,就重新计时。经典实现如下:
function debounce(fn, wait) { var timer = null; return function() { var context = this; var args = arguments; clearTimeout(timer); timer = setTimeout(function() { fn.apply(context, args); }, wait); }; }这段代码的关键是保存this和参数,确保执行原函数时上下文不丢失。如果漏掉context的保存,回调函数里的this就会指向window,这在对象方法的使用场景下会引入 bug。笔试中遇到这类手写题,建议先写基础版本,再补充this绑定和取消功能的选项,展示自己的细节意识。
深拷贝也是高频考题。基础版用递归遍历对象属性,判断属性值是否为对象类型再递归拷贝;进阶版要处理数组、Date、RegExp、Map、Set 等特殊对象,以及循环引用问题,常见做法是建一个WeakMap记录已经拷贝过的对象,遇到循环引用直接返回缓存结果。笔试如果时间有限,能写出解决数组和普通对象的递归版本就能保住基本分,加上循环引用处理则是加分项。
跨域相关的题目通常以简答形式出现。关键词是:同源策略、JSONP、CORS。需要理解的是“同源”指的是协议、域名、端口三者完全一致,只要有一个不同就属于跨域。JSONP 的原理是利用<script>标签不受同源策略限制的特性,通过动态创建 script 标签向服务器发起 GET 请求,服务端返回一段 JS 代码,调用约定好的回调函数并把数据作为参数传入。CORS 则是服务端在响应头中加Access-Control-Allow-Origin等字段,告诉浏览器哪些跨域请求是被允许的。两者本质上的区别是:JSONP 只能发 GET 请求且依赖服务端配合生成 script 内容,CORS 支持所有类型的请求,但要服务端正确配置响应头。
6. 备考策略与答题技巧
6.1 校招笔试题型的复习路径
如果你正在准备类似的校招笔试,我建议不要盲目刷题,而是以知识模块为单位进行系统复习。先把 JavaScript 的核心特性过一遍:作用域与闭包、this 绑定规则、原型与原型链、事件循环与异步编程、数组与对象的常用 API。每个模块配合 10 道左右的笔试题进行练习,重点不是记住答案,而是分析每道题背后的原理。
接下来是浏览器与网络部分:页面的完整渲染过程、重排重绘的触发条件、HTTP 缓存策略、跨域方案。这部分可以做笔记,因为知识点比较零散,笔试中更容易被遗忘的反而是这些细节,比如defer和async的区别、localStorage和sessionStorage的生命周期差异。我特别推荐把零散知识点整理成对比表格,临考前翻一遍效率极高。
最后是框架和工程化部分。Vue 和 React 二选一深入理解即可,笔试题目一般不会要求手写框架源码,但核心思想如数据响应式、虚拟 DOM、组件通信必须能说清楚。工程化方面要了解 webpack 的基本概念:入口、输出、loader、plugin,以及为什么需要构建工具来打包 ES6 模块和提取公共代码。
6.2 笔试答题时间分配与常见坑
一套前端笔试题的时间通常是一到两个小时,题量在 30 到 50 道之间,其中编程题 2 到 3 道。我的经验是先快速浏览一遍所有题目,把会做的选择和填空先做完,再接好处的简答题,最后集中精力做编程题。这样即使时间紧张,也能保证基础题目的得分率。
有一个坑需要特别提醒:选择题要仔细阅读题目中的“不正确”和“错误”等否定词。这类题目在命题时最容易被忽略,因为大脑在快速阅读时会自动过滤否定词,导致你选出了正确答案却仍然丢分。我的习惯是每道题都用笔圈出提问方向,是“正确的是”还是“错误的是”,在选项间犹豫时再回头核验一次。
另一个常见问题是手写代码不做边界判断。笔试的环境和编辑器不同,没有代码提示,很多候选人写function unique(arr)就直接开始循环,完全没考虑arr为空数组或arr不是数组的情况。虽然判题人可能不会扣很多分,但加上参数校验会让代码更加完整。同时,写完后花半分钟在脑子里模拟执行一遍,用空输入、单元素输入、重复元素输入各测试一次,能有效减少低级错误。
关于“这道题是什么意思”的困惑,我见过很多同学在闭包题目上卡壳,本质上是因为没有在纸上画作用域链。笔试允许打草稿,遇到复杂的作用域嵌套、闭包引用问题,一定要画图:把函数声明、变量声明、调用关系用箭头连起来,答案会变得非常清晰。这个方法我在真实现场屡试不爽,也推荐给正在备考的你。
7. 对 2017 年这套笔试题的复盘反思
现在回头看这套 2017 年的笔试题,其实能发现一个鲜明的时代特征:考察的重心仍然集中在 JavaScript 语言本身的特性上。ES6 刚刚普及但还不够成熟,很多开发者还在用var写代码,所以题目故意把var的变量提升、闭包捕获 var 变量的行为这些特性拿出来考。这和当前前端笔试的命题方向已经有了明显差异——如今的前端笔试更倾向于考察 TypeScript、设计模式、组件设计能力和性能优化方案。
但这并不意味着当年的题目过时了。恰恰相反,变量提升、this 指向、闭包、原型链这些内容依然是现代 JavaScript 开发者必须掌握的底层知识,因为在 React 的useEffect里捕获过期状态、在 Vue 的响应式系统中依赖收集的实现里,你仍然能看到这些经典机制的影子。
我建议每个准备校招的同学都找一份历年知名的前端笔试题完整做一遍,不管它是用友的还是其他公司的。做的时候不要只看正确答案,而是把错题对应的知识点整理成一份查漏补缺清单。这个复盘过程比刷十套新题更有价值,因为它能真实暴露知识体系中的薄弱环节。
最后分享一个我自己摸底时的小习惯:每做完一道笔试题,就在题目旁边用一句话写出考点是什么、出题人想考察什么能力。坚持下来你会发现,面试题库的换汤不换药程度比想象中高得多。等你积累了几十道这样的标注,走进笔试考场时,看到题目基本就能瞬间反应出它的考点和命题意图,这时候拿到满意 Offer 就只是时间问题了。