
最近整理旧电脑资料翻出当年整理用友2017校招web前端笔试题三的复盘笔记。现在看这些题目第一反应是“基础真重要”。那会儿前端面试还不像现在这样人人聊框架、讲工程化笔试的重心几乎全压在原生JS、浏览器机制、网络基础和代码基本功上。这套题我很喜欢因为它的出题节奏很典型选择题送分、简答题拉差距、手写代码直接定生死平时基础打得扎不扎实一份卷子就能看得明明白白。这篇文章我就按题型拆开讲说说每类题背后的考点是什么、答题思路应该奔着哪个方向走、当年我踩过哪些坑。内容主要面向正在准备校招或跳槽但原生基础还不够牢的web前端开发同学也适合想系统自查一遍前端地基的人。1. 先看懂这套题再谈刷题1.1 题型结构与考察目标这份笔试题的题型分布大致是基础选择题、简答与程序输出题、手写代码题以及少量开放性质的设计题。放在2017年这样的组合在校招笔试里非常主流现在很多公司也还在延续这个结构。选择题覆盖的知识面很宽HTML5、CSS、JavaScript基础、浏览器API、网络协议都会碰到但深度普遍不深。简答题开始进入正题闭包、this指向、事件循环这类JavaScript核心概念一定跑不了。最后的手写代码题是整张卷子的分水岭数组去重、深浅拷贝、防抖节流、继承实现都是高频题型。我当时复习到中后期才反应过来这类笔试题的真正目标并不是考“你背了多少API”而是考察三件事基础概念是否准确、能否用代码把想法落地、面对没有标准答案的问题时有没有自己的思路。很多同学刷题喜欢堆量但题海战术对校招笔试的收益没有想象中高因为你永远猜不到下一次会遇到什么题。反而是把核心概念背后的原理吃透任何变体都能接得住。1.2 2017年的前端环境为什么还考这么多原生基础这套题出现的时间点值得注意。2017年前后Vue 2.0已经发布了一段时间React在国内的社区热度也非常高组件化、工程化、构建工具这些概念开始大面积进入业务开发。但校招笔试题依旧把大篇幅留给原生JS和浏览器机制很多人不理解觉得“工作里都用框架谁还手写这个”。这个想法我当时也有后来踩过几次坑才明白框架是在原生语言之上封装出来的底层机制没理解透遇到线上问题就只能靠猜。举个例子不理解事件循环就说不清为什么setTimeout延迟不准确不理解原型链就很难真正搞懂Vue的响应式原理和React的合成事件机制不理解闭包写组件时很容易在定时器、事件监听器里制造内存泄漏。更重要的是校招求职者普遍没有太多真实项目经验面试官只能通过基础题来判断一个人的学习能力和代码素养。框架可以工作后快速学会但语言本身的运行机制、边界行为和工程素养往往才是拉开差距的地方。2. 选择题从HTML到JS送分题里全是细节2.1 HTML5与CSS3高频选择考点语义化、盒模型、优先级用友这套题的选择题部分HTML5和CSS3占比不小。HTML5方向常见的是语义化标签判断比如header、nav、article、section这些新增标签同时会混入font、center这种老标签作为干扰项问哪个不属于HTML5。这个点不难但要求对HTML5标签体系有整体认知而不是零散记忆。CSS部分两个高频坑必须提。第一个是盒模型正常情况下默认是content-box宽度只包含内容区域一旦设置了box-sizing: border-box宽度就直接包含了padding和border。笔试常考两类题一个是问你某个元素的实际渲染宽度是多少另一个是给两段代码让你对比布局结果。第二个高频坑是选择器优先级。CSS优先级规则并不复杂important 内联样式 ID选择器 类/属性/伪类选择器 元素/伪元素选择器但组合起来就容易犯错。比如.parent .child和#box .child同时命中同一个元素时类选择器写得再长也压不过ID选择器。我当时复习时给优先级做了一张速查表效果很好选择器类型示例权重等级!importantcolor: red !important最高覆盖一切常规声明内联样式stylecolor: red1000ID选择器#app100类/属性/伪类.box / [typetext] / :hover10元素/伪元素div / ::before1注意一点通配符*优先级是0继承样式的优先级比通配符还低。这些细节在选择题里特别容易变成坑。2.2 JavaScript基础选择typeof、变量提升与类型转换JavaScript基础选择题是整套卷子里最容易“看似会、实际错”的部分。最常见的考点是typeof返回值。typeof null的结果是objecttypeof undefined的结果是undefinedtypeof function(){}的结果是function。这几个边界值基本是必考项原因在于它们来自JavaScript语言设计之初的历史遗留只有真正看进去的人才会记住而不是靠猜。变量提升也是选择题常客。var声明的变量会被提升到作用域顶部但赋值不会被提升。我当时见到过一道类似这样的题console.log(a); var a 10;答案是undefined因为代码实际上被解释成var a; console.log(a); a 10;。如果换成let或const则会直接报错形成暂时性死区。这个对比能考出你对ES6新特性的理解程度。类型转换同样是必考。1 1等于多少3 * 2等于多少[] false是true还是false。这类题核心不是背答案而是理解JavaScript的隐式转换规则加号在处理字符串时会优先拼接其他运算符会尝试把字符串转为数字宽松相等会触发类型转换而严格相等不会。在笔试题里见到多半就是测试你有没有这个意识。2.3 浏览器存储与网络状态必考的简答衍生选择这套选择题还包含一批浏览器和网络基础题。localStorage、sessionStorage和cookie的对比很常见考法一般是给一组特性判断属于哪个存储方案或者直接问哪个API用于跨标签页共享数据。核心区别很清晰cookie体积最小且会随请求自动发送到服务端localStorage持久化存储且不自动上报sessionStorage在标签页关闭后就被清理。还有一点容易被忽略localStorage是同步读取的如果存了大量数据可能会阻塞页面渲染。HTTP状态码也是选择题常客。200是成功301是永久重定向302是临时重定向304是命中缓存403是禁止访问404是资源不存在500是服务端内部错误。2017年的题目里最常考的是301、302和304的区别因为这三个和浏览器缓存、页面跳转有直接关系。我现在复盘时觉得网络知识不会单独存在于选择题里它后面的简答题往往会让“实现一个资源缓存方案”或“说说从输入URL到页面展示发生了什么”。笔试考察本来就是环环相扣的选择题里的网络基础打不牢后面的大题也写不透。3. 简答与程序输出一道题就能看出基础扎不扎实3.1 闭包和内存释放答对一半是常态简答题里闭包几乎从不缺席。用友好几套题都出现过类似的表述“说说什么是闭包列举一个应用场景并说明闭包可能带来的问题。”闭包的定义说白了就是当一个内部函数引用了外部函数的变量并且这个内部函数在外部函数执行完后仍然被引用那么外部函数的作用域就被保留了下来这就是闭包。应用场景很多最典型的是数据私有化。比如写一个计数器希望变量只能通过特定方法修改不能从外部直接赋值。用闭包就可以做到function createCounter() { let count 0; return { increment: function() { count; return count; }, getCount: function() { return count; } }; } const counter createCounter(); counter.increment(); counter.increment(); console.log(counter.getCount()); // 2闭包带来的问题也很明确——变量长期驻留内存如果滥用并且没有及时释放引用就会造成内存泄漏。我在复盘笔记里给自己写了一句提醒闭包不是内存泄漏的原因不正确的引用管理才是。这部分答题时我建议采用“定义、原理、代码示例、应用场景、风险与优化”五段式结构。笔试阅卷时间有限结构清晰的答案比一大段没有重点的描述更容易拿分。3.2 this指向四种场景配合输出题一起记this指向问题在笔试里往往以程序输出题的形式出现纯文字简答反而少一些。考察核心是判断this到底指向谁。我总结为四种绑定规则调用方式this指向典型例子默认绑定非严格模式下指向全局对象window/global严格模式下为undefinedfunction test() { return this; } test();隐式绑定指向调用该方法的对象obj.method()中method里的this指向obj显式绑定由call/apply/bind指定fn.call(obj)中this指向objnew绑定指向新创建的实例对象new Person()中this指向实例规则本身好背难的是组合情况。比如var name window; var obj { name: obj, getName: function() { return function() { return this.name; }; } }; console.log(obj.getName()());答案是window。因为obj.getName()返回的是一个普通函数后续调用时它已经脱离了obj对象属于默认绑定。如果想让this指向obj最简单的方式是箭头函数或者在外层保存一份var self this。这类题目建议专门做一轮练习把默认、隐式、显式、new四种规则各找几个例子手推一遍再往组合情况上扩展。看得多了笔试遇到基本就是送分。3.3 事件循环与异步顺序从setTimeout追到Promise这套笔试题里有程序输出题非常经典放在今天依然是面试高频。我整理一个当时特别典型的版本console.log(start); setTimeout(function() { console.log(timeout); }, 0); Promise.resolve().then(function() { console.log(promise); }); console.log(end);正确答案是start、end、promise、timeout。很多同学第一次做都会答错把promise和timeout的顺序搞反。原因在于两者虽然都是异步但Promise的回调属于微任务事件循环会在当前宏任务结束后立即清空微任务队列而setTimeout属于宏任务需要等到下一轮事件循环才会执行。展开说事件循环的基本流程是执行一个宏任务然后执行全部微任务再根据情况决定是否渲染界面之后取出下一个宏任务执行。Promise.then、MutationObserver、queueMicrotask都属于微任务setTimeout、setInterval、I/O操作、UI渲染回调都属于宏任务。理解这个机制对实际开发非常有帮助。比如接口请求返回后需要更新多个状态如果直接在then里同步写大量代码可能会导致当前宏任务持续过长反过来如果需要把某些不紧急的任务推迟到下一轮可以放进setTimeout。笔试虽然只考一个输出顺序但背后的运行机制才是真正的考察目标。4. 手写代码题笔试能不能过基本由这里决定4.1 数组去重与扁平化一题多解见功力手写代码题里面数组去重几乎是我见到过的最常见题目用友这套也不例外。但同样一道题不同写法能看出完全不同的功力。最简单的写法是利用ES6 Set一行搞定function unique(arr) { return [...new Set(arr)]; }但如果笔试里只写这个在2017年还会面临兼容性问题即便今天也没法完全体现个人能力。更好的做法是掌握多级方案根据场景说明取舍。例如用filter加indexOf实现去重的同时还能保留类型判断function unique(arr) { return arr.filter(function(item, index) { return arr.indexOf(item) index; }); }如果数组里有NaNindexOf方案会失效因为NaN不等于自身。这时可以用Map实现function unique(arr) { const map new Map(); return arr.filter(function(item) { return !map.has(item) map.set(item, 1); }); }笔试时建议先把最简单的方案写出来然后补充一版更完善的方案并说明它们的差异。这比闷头写一个复杂版本更讨喜因为面试官能直接看到你的知识边界。数组扁平化也是同样的套路。先说结论可以用reduce递归实现function flatten(arr) { return arr.reduce(function(prev, curr) { return prev.concat(Array.isArray(curr) ? flatten(curr) : curr); }, []); }也可以利用ES6的flat方法直接arr.flat(Infinity)但同样需要讲清楚递归思路。手写题的考察点永远不是API本身而是你有没有理解数据结构的递归生成过程。4.2 防抖节流与深拷贝工具函数也需要手写底蕴防抖和节流是笔试题里另一对高频双雄。它俩的目标都是优化高频触发事件但策略不同。防抖是在事件停止触发后延迟执行适合搜索联想、窗口resize结束后的计算节流是限制在一定时间内最多执行一次适合滚动加载、按钮点击防重复提交。防抖的基础版本function debounce(fn, delay) { let timer null; return function() { const context this; const args arguments; clearTimeout(timer); timer setTimeout(function() { fn.apply(context, args); }, delay); }; }节流的基础版本function throttle(fn, interval) { let last 0; return function() { const context this; const args arguments; const now Date.now(); if (now - last interval) { last now; fn.apply(context, args); } }; }笔试里能写出这两个版本已经算不错。如果想加分可以进一步说明防抖的immediate参数是否立即执行一次、节流的定时器版本等。这些细节说明你对工具函数的边界情况有思考。深拷贝同样是高频手写题。最基础的方案是JSON.parse(JSON.stringify(obj))但很多同学不知道这个方案的三个致命伤会丢失undefined和函数、无法处理Date和RegExp对象、循环引用会直接报错。笔试现场写一个考虑基础的递归版本就够用function deepClone(obj) { if (obj null || typeof obj ! object) return obj; if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj); if (Array.isArray(obj)) return obj.map(function(item) { return deepClone(item); }); const result {}; Object.keys(obj).forEach(function(key) { result[key] deepClone(obj[key]); }); return result; }这里不需要写出完整支持环引用的工业级版本但必须知道什么问题存在、怎么去规避这是经验题。4.3 手写环节的踩坑经验边界、注释、时间分配手写代码题很多人不是不会而是写出来漏洞太多。我复盘时整理了几条自己的教训现在每次做题前都会过一遍边界条件优先考虑。数组为空、参数为null、字符串为空字符串这些情况先想清楚再写主体逻辑。笔试阅卷时边界条件往往是拿分和丢分的分界线很多人代码习惯性地把主流程写完就不管了一测试就翻车。变量命名要语义化。卷子上出现arr、res、temp没问题但最好能看出用途比如uniqueResult、flattened。遇到需要临时保存this的场景优先用箭头函数而不是var self this因为箭头函数在ES6环境更合适代码也更干净。时间分配上手写题建议留足20到30分钟。如果一道题卡住超过10分钟先跳过写下一题最后再回来补。笔试最怕在一道题上死磕导致后面会的题没时间写。另外有一个非常实用的技巧写完代码后不要立刻交卷把变量代入代码走一遍。比如数组去重的函数拿[1, 1, 2, undefined, null, undefined]在脑子里模拟一遍很多错误能当场发现。5. 从这套题延伸出来的复习思路5.1 别急着背框架先把知识地图画全很多准备校招的同学一上来就冲Vue源码、React Hooks结果基础选择题错一片。这是典型的复习优先级问题。框架随时可以学但HTML、CSS、JavaScript、浏览器机制、网络基础这五块是笔试题稳定的基石。我当时给自己定了一个复习顺序先把JavaScript语言核心过一遍包括数据类型、作用域、闭包、原型链、异步机制然后整理CSS常见布局方式和选择器优先级接着梳理浏览器渲染流程和缓存机制最后再回头补网络协议和Web安全常识。这套顺序让我在复习中期心里就比较有底了。知识地图怎么搭建我的做法是拿一张白纸从“JavaScript”中心词出发一级分支列出语言基础、DOM/BOM、异步编程、面向对象、ES6每个分支再继续往下拆。拆到第三层时如果某个知识点不能在三句话内解释清楚就是需要补课的薄弱项。5.2 失分点复盘与高频问题速查复盘这套笔试题时我把常见失分点整理成了一个速查表每次考前过一遍知识点常见失分点正确打开方式CSS优先级混淆类选择器和ID选择器的权重先算权重再比较important和行内样式单独考虑闭包只写概念不写应用和风险定义、原理、示例、应用场景四点完整陈述this指向忽略严格模式差异分清默认/隐式/显式/new四种绑定事件循环把Promise当作普通回调微任务在当前宏任务结束后全部执行宏任务下一轮执行数组去重只写Set版本不考虑NaN和兼容性多准备一版兼容方案并说明适用场景深拷贝依赖JSON方法忽略坑点掌握递归实现同时能说出JSON方案的局限性这张表不是复习内容的全部但它能帮你快速定位问题。每次模拟笔试后把错题归到某个知识点下针对性地补视频、补文档、补练习比盲目刷新题有效得多。5.3 现在再看这套题我的几点体会重新看这套笔试题我最大的体会是基础题的考察方式变了但考察内容几乎没有变。现在前端开发越来越依赖框架和工具链但做框架的人也是写JavaScript的。Vue的响应式系统依赖的是Object.defineProperty或ProxyReact的调度机制依赖的是任务队列和时间切片这些底层能力恰恰是笔试在考的东西。所以不要嫌笔试题目“老”它们其实是在替你检查真的能脱离框架独立解决问题吗第二个体会是手写代码题不只是考代码更考表达。笔试阅卷人看不到你敲代码的过程只能看结果。所以变量名、注释、代码结构都能成为加分项。我当时习惯在关键函数上方写一行注释说明思路这个习惯后来在面试里也被面试官主动提起过说“看代码就知道这个人有整理思路的习惯”。最后想分享一个小技巧把做过的手写题整理成标准答案集反复重写。不是背代码而是每次重写都问自己几个问题——如果入参换成空值怎么办如果数据量变大怎么优化如果用ES6重写会怎么改。一个题目能吃透这三个问题比应付十道新题更值钱。这套用友笔试题给我留下的最大价值不是那些题目本身而是让我形成了这种“多问一步”的复习习惯直到今天还在受益。