公司动态
用友2017校招前端笔试题解析:JavaScript核心考点与浏览器原理
如果经历过那几年的校招季大概率会对这类题目有印象前端笔试不是让你默写 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/bindvar 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通知依赖更新对应的 DOMv-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 就只是时间问题了。