
一个下午的时间我在电脑前把用友2016校招的web前端笔试题从头到尾梳理了一遍。这套题放在今天看难度不算变态但覆盖面相当全JavaScript基础、DOM操作、浏览器原理、性能优化全都有涉及。更关键的是这套题反映了那个时代前端校招的典型考察思路——不考框架API的死记硬背而是看你基本功扎不扎实、能不能用原生JS解决实际问题。我结合当年的参考答案和这几年的实际开发经验把每道题的考点、答题思路、易错点都拆开揉碎聊一遍。准备校招的朋友可以拿这套题自测一下工作三五年的老前端也可以看看自己还能答上来多少。1. 这套题背后的考察逻辑老牌企业校招到底想要什么人很多同学拿到笔试题的第一反应是赶紧找答案、背答案但我建议你先花十分钟想想出题人到底在考什么。用友不是互联网大厂是典型的企业服务软件公司业务以ERP、云服务为主。这类公司的前端团队规模不会特别大但负责的业务系统往往逻辑复杂、生命周期长一个页面可能要用好几年。所以他们的校招笔试题有几个很明显的倾向。首先极度看重原生JavaScript的掌握程度。2016年的时候jQuery还是绝对主流Vue和React刚开始普及但远没有形成垄断。企业级项目里有大量存量代码是原生JS和jQuery写的你进来之后最可能要做的就是维护这些老系统。所以笔试题里大量出现this指向、作用域、闭包、原型链这类纯语言层面的问题——这些不会因为框架更迭而过时。今天来看虽然大家写Vue、React多了但原生JS的基础考察依然是前端面试的保留项目足以说明这套题的生命力。其次注重考察解决问题的思路而非单纯的知识记忆。这套题里有不少是代码输出题或手写实现题比如让你判断一段代码的输出结果、让你实现某个数组去重。这类题目如果只是背过答案换个形式就会露馅。出题人想看到的是你分析代码执行过程的思路比如遇到this指向问题你会不会先看调用方式遇到闭包题你会不会先标出变量的引用关系。第三关注工程实践中的常见场景。事件绑定、DOM操作、数据请求、字符串处理这些都是做企业级前端每天都会遇到的事情。笔试题不考脑筋急转弯而是把实际开发中的典型场景抽象成题目。我在做这套题的时候就有这种感觉——很多题不是“会不会”的问题而是“平时写代码的时候有没有想过为什么会这样”。比如事件委托如果你只是在框架里写click绑定从来没想过事件冒泡的机制遇到这类题就很容易卡壳。所以如果你现在正在准备前端校招看到一套笔试题不要急着刷完对了答案就完事。先看题型分布再对着每道题问自己三个问题它在考什么知识点这个知识点在实际开发中解决什么问题我能不能用大白话把原理讲清楚这三关过了笔试面试基本就稳了。2. 题目逐题拆解从JavaScript基础到浏览器原理2.1 数据类型与类型判断typeof与instanceof的相爱相杀第一类必考题就是数据类型相关的用友这套题里也出现了。给你一段代码让你用typeof判断各种值会输出什么或者让你说说null、undefined、NaN的区别。这类题看着简单但想答得全面并不容易。typeof的返回值有string、number、boolean、object、function、undefined这几个2016年那时候还没有symbol。很多人知道typeof null返回的是object但不知道这是JavaScript语言设计时留下的历史bug——早期的实现中对象类型用低三位表示null恰好是000所以被typeof当成了对象。知道这层底层原因之后你就不容易忘记了。NaN的判断是另一个高频点。NaN有一个很反直觉的特性——它不等于自己。所以你不能用n NaN来判断标准做法是调用全局的isNaN函数或者更严谨地用n ! n。用友的题里就有一道类似的判断当时很多人栽在null和NaN上。还有一种判断方式是ES6的Number.isNaN它跟全局isNaN的区别在于后者会先把参数转成数字再判断前者直接判断类型所以Number.isNaN(abc)是false而isNaN(abc)是true。这个细节在实际开发中也很容易踩坑。再说instanceof。它的原理是判断构造函数的prototype是否出现在实例的原型链上。所以[] instanceof Array返回true[] instanceof Object也是true。但有个坑是跨iframe的情况下一个iframe里的数组在另一个iframe里用instanceof判断可能是false因为原型链不同了。这也是为什么后来有了Array.isArray这个方法。我在实际开发中遇到过一次类似的问题页面里嵌了个iframe用它传数据过来结果instanceof判断把所有数组都当成了非数组排查了半天才想起来还有这个历史遗留坑。2.2 作用域与闭包为什么你的for循环i等于最后一次的值用友这套题里最经典的一道就是循环里绑定事件的题目。类似这样一个for循环里给三个元素绑定click事件循环变量是var i问点击每个元素的时候弹出来的是几。答案是每次都是最后一个因为var声明的i是函数作用域循环结束之后i已经是最终值了事件回调执行时访问的是同一个变量。这个问题的本质是闭包捕获的是变量本身而不是变量的值。解决方式有几种用let声明循环变量让每次循环创建独立的作用域或者用立即执行函数(IIFE)传入当前的i也可以用bind方法给回调函数绑定参数。用友当年的题标准答案是IIFE方案因为那时候ES6的let还没那么普及。现在做这类题直接写let就完事了但你最好把三种方案都掌握因为面试官很可能追问。闭包还有一个非常实用的场景就是私有变量。比如实现一个计数器外部不能直接修改内部的值只能通过暴露的方法来操作。用友也考了这个——让手写一个简单的模块或计数器。这种题的本质就是考察你能不能理解和运用闭包的两个特性一个是被闭包引用的变量会常驻内存另一个是闭包可以形成对变量的独占访问。这里要提醒大家一个容易忽略的点闭包并不神秘它就是函数嵌套函数时内层函数可以访问外层函数变量的机制。很多初学者一听到闭包就紧张其实你每天写的回调函数绝大多数都是闭包。判断是不是闭包就看函数是否访问了它自己作用域之外的变量仅此而已。2.3 原型与原型链new一个对象到底发生了什么原型链是JavaScript里最绕的一块内容但也是老牌公司特别喜欢出题的部分。用友的题目里有一道给你一个构造函数然后让你判断某个对象有没有某个属性、属性能不能访问到或者让你说说new操作符到底做了什么。标准的new过程分四步创建一个新对象把这个新对象的原型指向构造函数的prototype把构造函数的this绑定到新对象上并执行构造函数代码如果构造函数有返回对象则返回这个对象否则返回第一步创建的新对象。这个流程理解之后很多题就能推出来了。还有一个高频考点是hasOwnProperty用来判断属性是实例自有的还是从原型链上继承来的。这在实际开发中很重要比如你用for in遍历对象时最好用hasOwnProperty过滤掉原型上的属性。用友的题里就有一道辨析让你说出for in会遍历到什么样的属性。原型链还有一个常见用法是做继承。ES6的class本质还是基于原型链的语法糖。题目可能会让你用ES5实现一个继承一般有构造函数继承、原型链继承、组合继承几种方案。组合继承是当时的标准答案它既能通过call在子类构造函数里继承实例属性又通过把子类的prototype指向父类实例来继承方法。现在虽然都用extends了但原理掌握了对理解React组件的继承关系还是很有帮助的。2.4 DOM操作与事件机制浏览器页面的读写规则企业级前端写的最多的就是DOM操作用友这套题里DOM相关的题目占了相当比重主要围绕节点查找、事件绑定、事件委托。题目类型包括给你一个ulli的结构让你实现点击某个li的时候弹出行号或者让你删除一个节点问有几种方式。节点查找那几个方法——getElementById、getElementsByClassName、getElementsByTagName、querySelector、querySelectorAll——在那个年代是基本功。注意getElementsBy系列返回的是HTMLCollection是动态集合意思是每次访问它都会重新查询DOM。querySelectorAll返回的是NodeList是静态集合页面变化不影响它。这个区别在面试中问出来很容易区分有经验和没经验的人。实际开发中的坑在于你用getElementsByClassName拿到一个集合然后在循环里做删除操作会发现索引错乱就是因为集合是动态的。事件机制的核心是捕获、目标、冒泡三个阶段。用友考了冒泡和捕获的差异以及如何阻止冒泡。当年标准答案是e.stopPropagation()现在还有人用e.cancelBubble true这种老写法。事件委托就是充分利用冒泡机制的经典实践——给父元素绑定事件通过e.target判断实际点击的元素。这在动态生成的列表里特别实用新添加的子元素不用重新绑定事件。这里需要特别注意一个细节很多面试者能说出事件委托的概念但实现的时候没有考虑边界情况。比如做输入框防抖的时候如果输入框在事件委托的目标范围内你需要判断e.target是不是input元素否则会在用户点击input标签时也触发。这类细节通常决定了面试官对你能力的评价。2.5 浏览器机制与网络基础一个前端的基本常识储备用友的笔试题还涉及了不少浏览器和网络的基础知识比如URL从输入到页面展示的过程、常见的HTTP状态码含义、cookie和localStorage的区别、前端性能优化方案等。这类题目在那个年代属于加分项现在则是必考内容。URL从输入到展示的过程标准回答链路是DNS解析 - 建立TCP连接 - 发起HTTP请求 - 服务器处理并返回 - 浏览器解析HTML构建DOM树 - 解析CSS构建CSSOM - 执行JavaScript - 合并渲染。这里面可以展开的点很多比如DNS解析是递归的还是迭代的、TCP的三次握手和四次挥手、浏览器的渲染流程里什么时候会阻塞DOM解析。用友当年的题没问那么深但你回答得越有层次面试官对你的评价就越高。HTTP状态码是必须知其然且知其所以然的200是成功301是永久重定向302是临时重定向304是走缓存403是禁止访问404是找不到资源500是服务器内部错误502是网关错误503是服务不可用。做题的时候最怕遇到的是304和重定向的区分——很多项目上线之后出现资源不更新的情况就是因为没有正确处理缓存头导致走了304。localStorage和cookie的区别也是高频题存储大小不同、会不会随请求发送到服务器、过期时间设置方式不同、作用域差异。特别要记住随着业务越来越复杂cookie里尽量不要塞太多非必要数据因为每个请求都会带上它们极大地浪费流量。后来有了HttpOnly和SameSite属性这个知识点也在校招中出现过你需要知道它解决了什么问题。3. 写代码的题看着简单但最容易翻车的几类3.1 数组去重与字符串处理一题多解是核心竞争力用友这套题里有几道手写代码题数组去重算是非常典型的一道。这类题的神奇之处在于每个人都能写出来但写法不同体现了水平差距。最简单的方案是双重循环去重外层遍历数组内层逐个比较是否已存在于新数组中。这个方法时间复杂度是O(n²)数据量小的时候无所谓但作为笔试题这个答案只能说明“你能写代码”不能说明“你写得好”。那时候的主流优化方案是用对象作为哈希表来去重——把数组的值存为对象的key利用对象key的唯一性判断是否重复。性能快到飞起但有个历史坑对象key只能是字符串所以数字1和字符串1会被当成同一个key。解决方式是obj[typeof item item]来区分类型。用indexOf去重是当时很多人爱用的遍历原数组如果新数组里没有当前项就push进去。这在大多数场景下没问题但遇到NaN就失效了因为indexOf内部用的是严格相等比较而NaN不等于任何值。用includes可以解决NaN的问题用ES6的Set更是只要一行代码。但笔试的时候不要一上来就写Set我建议你先写出多种方案再和面试官聊聊每种方案的优劣反而能展示你的广度。字符串处理题目也经常出现比如统计字符串中出现次数最多的字符、判断回文、反转单词等。这类题的核心是能不能熟练使用split、join、reverse、replace、match这些API以及能不能处理边界情况。以统计字符次数为例标准思路是遍历字符串用对象记录每个字符的出现次数最后找出最大值对应的key。这道题本身不难但它能考验你能不能快速想到用对象当计数器的方法。3.2 数组与对象的方法混用map、filter、reduce不只是语法糖2016年的时候ES6的数组新方法已经慢慢普及了用友的题目里也出现了map、filter、reduce相关的题。形式一般是给你一段代码让你说出输出结果或者让你用指定的方法实现某个功能。map和forEach的区别是常考的map会返回一个新数组forEach只遍历不返回。很多人能用它们写出代码但一问区别就卡壳。filter的作用是筛选出满足条件的元素返回新数组。reduce是最灵活但最难掌握的——它可以把一个数组归约为任意值常用于求和、对象分组、多维数组扁平化。用友有一道题就要求用reduce实现数组扁平化本质是在遍历的过程中把每一次的当前项拼接进累加器。这个题可以一层层追问如果数组是任意深度的呢加个递归就可以解决。需要特别提到的知识点是parseInt和map组合的坑。有名的一道题是[1, 2, 3].map(parseInt)输出结果是[1, NaN, NaN]。原因很简单当map调用parseInt时传给它的是两个参数——当前值和索引而parseInt接收两个参数——字符串和进制基数。所以第二次调用相当于parseInt(2, 1)进制基数最小是2传1返回NaN。这个坑到现在依然常见它考察的是对函数参数的敏感性——有没有意识到map的回调函数会被传入额外参数。3.3 深浅拷贝为什么直接赋值不是拷贝深浅拷贝是笔试常客用友这套题里也有。考法通常是给你一个嵌套对象让你实现深拷贝或者让你说说直接赋值、浅拷贝、深拷贝三者的区别。直接赋值obj2 obj1时obj2和obj1指向同一个内存地址改任何一个都会影响另一个。浅拷贝只拷贝第一层属性如果属性值是对象或数组那拷贝的还是同一个引用。深拷贝则连嵌套的对象也拷贝完全独立。实现深拷贝有几种思路。最简单粗暴的是JSON.parse(JSON.stringify(obj))但它有几个致命缺点函数、undefined、symbol会被忽略日期对象会变成字符串正则表达式会变成空对象存在循环引用的时候会直接报错。所以面试时如果你用了这个方法最好主动把坑说出来然后再手写一个递归深拷贝遍历对象的每一项如果是对象或数组就递归处理否则直接赋值。手写递归深拷贝也需要考虑循环引用问题用一个WeakMap记录已经拷贝过的对象遇到重复就直接返回引用避免死循环。这个改进点能体现工程经验在面试中属于加分项。3.4 防抖与节流性能优化的基础武器前端性能优化里防抖(debounce)和节流(throttle)是绝对不能不知道的两个概念用友的题也考到了。防抖的思路是事件触发之后不立即执行而是等待一段时间如果这段时间内又触发了就重新计时。典型的场景是搜索框输入联想——用户停止输入一段时间后再发请求避免每敲一个字母都请求一次。节流跟防抖不同它是保证在一段时间内最多执行一次。典型场景是滚动事件里做懒加载——用户滚动速度很快但你不想每次都执行计算保证每200毫秒最多执行一次就行。手写实现的话防抖的核心是一个定时器变量。每次事件触发先清除旧的定时器再重新设置定时器延迟执行。节流的实现方式有两种——时间戳方式和定时器方式。时间戳实现是记录上次执行的时间如果当前时间减去上次时间超过设定值就执行定时器实现类似防抖但要配合一个标志位确保定时器存在的时候不再创建新的定时器。我在项目里用的基本是自己封装的这两个函数Node环境或者浏览器环境通用处理滚动、输入、窗口resize这些场景非常好用。4. 从笔试题延伸出去的前端面试准备策略4.1 版本差异和时代背景2016年的题放到现在该怎么看回顾这份笔试题有些题今天已经属于基础中的基础有些则因为技术演进不再是主导。比如jQuery相关的内容2016年几乎是必考现在反而很少出现在校招笔试题里了。但你不能完全不管它——很多老项目依然在用jQuery维护你简历上写了熟悉jQuery的话面试官依然可能揪住你对源码的理解不放。ES6的新语法在2016年算是加分项如今则已经变成默认要求。let/const、箭头函数、解构赋值、Promise、async/await、模板字符串、模块化这些东西不只是能用还得明白它们要解决什么问题。比如箭头函数没有自己的this它继承外层作用域的this这在React类组件的回调里和事件监听器里特别重要——你不需要再写var that this了。现在的笔试题跟2016年相比多了这些方向Vue和React框架的原理题、工程化工具链的配置题、TypeScript类型推导题、Node.js相关的后端题。但从本质上看考察的还是你对前端基础原理的理解深度。框架更新换代很快但HTML、CSS、JavaScript的核心原理变化不大。所以准备的时候我的建议是以原生技术为主干框架理解为枝叶。4.2 题型的底层共性如何用一套方法应对所有校招笔试题我在看这套题的时候发现虽然题目很杂但解题方法有一个共通路径。第一步读懂题目在考什么——是纯粹的知识记忆还是需要你现场推理。知识记忆题送分题必须全拿比如状态码含义、API用法。需要推理的题比如代码输出类一定要在草稿纸上把执行过程一步一步写出来不要凭直觉答题。第二步写代码的时候先想边界条件。数组为空怎么办传参类型不对怎么办要排序的数组里有重复元素怎么办很多同学笔试翻车不是不会做而是代码里没有处理空值和异常。面试官看你的代码最先看的就是边界情况的处理能力因为在真实业务中接口返回的数据千奇百怪处理不了空数据的前端代码上线必崩。第三步做错题之后一定要复盘。这个复盘点不是“哦答案是这个”而是“我为什么没想到这里是这个”。大部分时候是因为某个知识点存在盲区或者是对某个API的参数理解不透彻。这时候把这个知识点单独拎出来找三五道相关的题练熟。第四步也是最容易被忽略的——把知识点串成体系。比如闭包连着作用域链和this指向一起理解事件委托连着事件冒泡和DOM结构一起理解。单点知识容易忘体系化的知识才不容易忘记。4.3 每一道题都是一个面试聊天的引子用友这套题还有一个很有意思的用法——把每一道题当成一个面试话题的起点。比如数组去重那道题你可以主动展开如果数据量特别大该选哪个方案如果数组里有对象元素呢如果是类数组呢实际上这些问题在真实业务中都可能遇到。你提前多想几步面试的时候就能更主动地引导话题展现出你有独立思考和深入研究的习惯。再比如for循环绑定事件的题你可以顺带说一下现在用let解决了但当时的痛点是什么也可以提到React和Vue的列表渲染为什么不需要手动处理这个问题——因为框架做了虚拟DOM的diff事件统一绑到了根节点上。如果你能把这些知识点串起来讲面试官会觉得你不仅懂原理还有框架层面的视野。准备前端校招本质上就是建立知识体系和提高解题速度的过程。这套2016年的用友笔试题值得认真做但不建议只对着标准答案背而是要理解每个题目背后的考察逻辑。离面试还有时间的同学按我上面总结的路径来一遍效果会很明显。最后分享一个我自己摸索出来的小技巧准备笔试的时候不要只做题每道题做完之后给自己出一个“变形题”然后尝试解答。比如原题是数组去重你可以问自己去重后还要保持原顺序怎么办去重的时候要返回重复次数最多的元素怎么办这样把一个点扩成一类题覆盖面就大了很多。