拓冰建站拓冰建站
首页 / 资讯中心 / 正文

同程前端笔试卷全解析:从原型链到手写深拷贝的考点拆解

2017年的金九银十很多应届生都在投简历前端这个岗位在那会儿已经卷出一定高度了但还远没有到如今“八股文算法工程化”全方位轰炸的程度。同程这份试卷放在当年算是相当有代表性的那一类不偏门不炫技考的全是一个前端工程师每天都要打交道的基本功但每一道题都能往下追问好几层。我当时拿到这套题的时候做了个整体评估它其实就在干一件事——把那些简历上写着“精通JavaScript”的人筛掉。这份试卷覆盖了HTML/CSS基础、JavaScript核心机制、手写代码、以及开放性的项目与性能优化题整体难度中等偏上考察重点非常明确语言基础是否扎实、编码习惯是否良好、到底有没有真实写过项目。现在回头看它考察的能力模型依然不过时反而在今天工程化工具链越来越重、框架层封装越来越厚的情况下这套基础题的参考价值被放得更大了。这篇文章就来完整拆一下这份试卷的解题思路、背后的考察逻辑以及我在准备这类笔试时积累的实操经验。1. 试卷整体布局一份笔试卷子的“能力筛子”长什么样前端校招笔试和社招面试有一个很大的不同校招没有真实项目背书面试官只能通过笔试去推断候选人的基础是否牢固、学习是否深入、代码风格是否靠谱。所以一份好的校招笔试卷本质上就是一个能力筛子每一道题都对应着一个特定的能力维度。1.1 试卷结构拆解四类题型的定位同程这份试卷整体上可以分为四个大块模块题量占比考察核心筛选意图HTML/CSS基础约25%布局、盒模型、兼容性筛掉只写过框架、没写过页面的人JavaScript基础约35%作用域、闭包、原型、异步筛掉“会用但说不清”的人手写代码约25%去重、排序、深拷贝等筛掉只看不练、眼高手低的人开放题/综合题约15%性能优化、项目描述筛掉没有真实项目经验的人这个结构分配很有意思JavaScript占比明显高于HTML/CSS说明岗位侧重逻辑能力和语言理解深度。在实际考试中时间分配也需要心里有数基础题不要恋战编程题至少要留出40%的时间开放题要写得有条理不能空泛。1.2 为什么“基础题”反而是翻车重灾区我见过太多人在准备校招笔试时把大量时间花在刷算法题上结果一看到“解释CSS盒子模型”就懵了。这种情况在2017年特别普遍因为那会儿培训班大量输出“会写页面、不懂原理”的学员笔试恰恰是筛掉这批人最有效的方式。其实这种题有一个隐藏的考察点你能不能把一句话的概念展开成一个系统性的回答。比如“盒子模型”这四个字展开来说应该是标准盒模型和IE盒模型分别指什么、box-sizing的属性值有哪些、width的计算规则、margin折叠的问题、以及在实际布局中如何统一盒模型。如果你在试卷上只写了两行字那不管是知识储备还是表达条理性都会被扣分。2. HTML/CSS题不只是写布局还看你有没有“工程师思维”这份试卷的HTML/CSS部分考察重点非常集中盒模型、浮动、定位、居中方法以及浏览器兼容。这些内容在2017年都是面试必考现在依然是前端基础的重头戏因为CSS布局是页面呈现的根基。2.1 经典题型盒模型计算与box-sizing题目示例给定一个div宽度为200px左右padding为20px左右border为2px问标准盒模型和IE盒模型下元素实际占位宽度分别是多少标准盒模型下width只包含content宽度所以实际占位宽度 200 202 22 244。而IE盒模型也叫怪异盒模型下width已经包含了padding和border所以实际占位宽度就是200但content内容区宽度会压缩为 200 - 202 - 22 156。这里有一个面试官非常喜欢追问的点实际开发中如何统一盒模型答案是*, *::before, *::after { box-sizing: border-box; }这个全局重置。为什么要统一因为视觉设计师给的标注尺寸是包含边框和内边距的如果每个元素默认都是content-box那写起来到处都要做减法太容易出错了。这个放在2017年其实是很有争议的一个选择很多老项目还在用content-box但统一border-box已经是大势所趋现在基本是无可争议的开局配置了。实操心得答题时不要只写答案要在旁边标注上计算过程。面试官看卷时计算过程比结果更能体现你的逻辑严谨性。2.2 布局题清除浮动的方式有几种试卷里会有这样的题浮动元素导致父容器高度塌陷请列出至少三种清除浮动的方法。这道题在2017年是高频题目因为那会儿flexbox虽然已经普及但老项目里浮动布局仍然大量存在。回答时至少要给出这几种在浮动元素末尾添加空元素设置clear: both。父元素使用overflow: hidden或overflow: auto。父元素使用伪元素::after { content: ; display: block; clear: both; }。父元素自身也设置浮动。父元素设置固定高度。如果只是列出来那只能拿基础分要拿高分还得说出它们各自的适用场景和隐患。比如overflow: hidden最大的隐藏坑是如果子元素有需要溢出的内容下拉菜单、tooltip会被直接裁切掉而空元素的方式会污染HTML结构增加无意义的标签伪元素方式在语义上最干净兼容性也是从IE8开始就是OK的是团队项目中最推荐的方案。其实这道题更深的意图是考察你是否理解“BFC”这个概念。清除浮动的本质是什么是让父容器形成一个BFC块级格式化上下文。overflow: hidden能清除浮动是因为触发了BFC而BFC的一个重要特性就是会包含浮动的子元素。能回答到这一层和只是背了三种方法的应试者水平差距就很明显了。2.3 兼容性题CSS hack与渐进增强2017年虽然IE9/10/11还在但移动端已经开始爆发所以试卷里偶尔会出现“浏览器兼容策略”这类题。我记得有一个题目是问一个功能需要兼容IE8及以上你会怎么做这道题的考察点其实不是你会不会写CSS hack而是你脑子里有没有兼容性治理的整体思路。合格的回答应该分三部分第一用can i use等工具查兼容性第二选择渐进增强还是优雅降级的策略第三具体到代码层面用supports、特性检测、或者Class前缀的方式打补丁。那会儿我用的一个常见方案是基础布局用CSS3实现给支持CSS3的浏览器标注supports不支持的就回退到浮动布局。同时用modernizr做特性检测在html标签上打类名然后再根据类名写不同的样式规则。现在来看这套思路有点重了但当时的兼容性土壤就是这个样子。避坑提醒不要在试卷上写“用CSS hack”。这个词本身带有负面色彩它意味着你在靠奇技淫巧解决问题而不是在治理问题。正确的表达是“渐进增强与优雅降级策略的取舍”哪怕你内心确实是在用hack。3. JavaScript核心机制能不能吃前端这碗饭关键看这里JavaScript部分在这套试卷里占的比重最大也是区分度最高的板块。这不是偶然的前端工程师的核心竞争力就在于对JavaScript语言本身的理解深度。框架可以换语言基础才是根。3.1 作用域与闭包一道题能挖出三重理解经典的题目形式是写出以下代码的输出结果并解释原因。for (var i 0; i 5; i) { setTimeout(function() { console.log(i); }, 100); }输出结果是 5 个 5不是 0 1 2 3 4。原因有两个层面的理解第一var声明的i是函数作用域for循环不会创建新的作用域第二setTimeout的回调函数在循环结束之后才会执行此时i早就变成了5。只要解释到这两层这道题就算过关了。但如果想拿高分要再往后延伸一层怎么改才能输出 0 1 2 3 4解法有三种。第一种是ES6的let取代var每次循环会创建一个新的词法环境第二种是IIFE立即执行函数在每次循环时把i作为参数传进去形成闭包第三种是用setTimeout的第三个参数把i作为参数传进回调。这三种方式里let是最干净最优雅的也是后续ESLint会自动提示的写法。这道题还有一个升级版本我在同程的笔试里就遇到了类似的如果将var换成let输出是什么答案是 0 1 2 3 4。为什么因为let在循环迭代中会为每一次循环创建独立的词法绑定。这个知识点在2017年的时候很多人其实是背结论而不是真正理解但真正理解的人才能讲清楚“词法环境”这几个字。3.2 原型与继承给你一支笔自己画一张图原型链是前端笔试的“保留节目”。题目往往是定义一个Animal函数然后创建一个Dog函数继承Animal要求写出完整代码并说明实例、构造函数、原型对象三者之间的关系。这个题型让我印象很深因为很多人在前端框架里泡了很久一提到原型链就含糊。标准解法其实是这样function Animal(name) { this.name name; } Animal.prototype.say function() { console.log(this.name); }; function Dog(name, breed) { Animal.call(this, name); this.breed breed; } Dog.prototype Object.create(Animal.prototype); Dog.prototype.constructor Dog; Dog.prototype.bark function() { console.log(汪汪); };每一步都有讲究Animal.call(this, name)是把父类的实例属性复制到子类实例上解决的是属性继承Object.create(Animal.prototype)是把子类的原型指向父类原型解决的是方法继承Dog.prototype.constructor Dog是把constructor指针修正回来如果不加Dog实例的constructor会指向Animal这在某些循环遍历场景下会出现问题。答题时如果能顺手写上“为什么不直接用Dog.prototype new Animal()”那就是加分项了。因为直接new会导致子类原型上多出一份父类实例属性这些属性其实会被实例属性遮蔽掉是多余的。而且如果Animal构造函数里有逻辑依赖可能在继承时就触发副作用。用Object.create就干净很多。就这个细节面试官一问一个准。经验分享校招笔试阶段不要只写代码要在代码旁边用一两句话标注你的设计意图。很多批改人不会逐行执行你的代码他们看你写不写得出关键设计点。3.3 异步与事件循环宏任务和微任务的排队逻辑2017年的异步考题主要集中在回调地狱怎么解决、Promise的基本用法、以及setTimeout的执行顺序。其中有一类题我印象很深console.log(1); setTimeout(function() { console.log(2); }, 0); Promise.resolve().then(function() { console.log(3); }); console.log(4);输出顺序是 1 4 3 2。第一眼可能觉得奇怪setTimeout明明设置了0延时怎么反而跑到Promise后面了这是因为事件循环里任务是分优先级的同步代码执行完进入微任务阶段微任务队列清空后才轮到宏任务setTimeout属于宏任务。Promise.then在微任务队列中所以它比setTimeout的输出更早。这道题在2017年属于高难度的细节题放到现在基本上是人人都要会的送分题。但正是这种“从难到易”的过程说明了它的价值它考察的是你对浏览器事件循环机制的理解而不是背API。现在字节、阿里这些大厂会把这道题不断变形加入async/await、requestAnimationFrame、MutationObserver等考的是你对整个异步执行队列的掌握程度。所以建议准备笔试时不要只背一个顺序结论要彻底理解宏任务、微任务的分类和切换规则。4. 编程题实战除了“写对”还要“写好”编程题一般有两到三道有些是大题套小题先让写一个功能函数再让补全边界条件最后写测试用例。我见过很多人前两步都做得很好到写测试用例的时候随便写了两个正常输入完全没有考虑边界情况。这一眼就能看出来是缺少真实的工程经验。4.1 手写数组去重从两行到十行数组去重是前端笔试里出场率最高的题没有之一。题目往往不指定实现方式直接说“请写出数组去重的方法”。2017年的试卷里这题几乎是必备的。暴力解法是嵌套循环时间复杂度O(n^2)虽然能跑但在笔试里拿到这种答法基本说明基础不过关。稍好一点的是用对象键值对或者Set一行代码就搞定const unique arr [...new Set(arr)];这个解法简洁高效但注意它有前提数组元素是原始类型。如果数组里有对象Set的去重就失效了因为对象是引用类型每个对象的引用都不同。所以真正要展示自己水平的话应该针对不同情况给出分层方案原始类型用Set对象数组根据某个唯一标识字段去重。对象数组去重的方法也有两种思路用Map JSON序列化做深比较或者用reduce 辅助对象记录已有标识。生产环境里需要根据字段去重的情况多得很比如接口返回了一个列表前端要根据id字段去重。如果笔试做到了这一层的讨论那就已经把这道题的分数吃透了。我当时在笔试里额外补充了一段说明“Set去重适用于基础类型对象数组需要按字段去重并考虑了NaN的去重问题。”从后来跟面试官聊天的情况来看这类主动思考的注释确实能加印象分。4.2 深拷贝手写的时候才能发现的知识盲区手写深拷贝堪称笔试卷里的“题眼”。这道题表面上在考递归实际上考的是数据类型的完整认知、边界的处理能力、以及工程性考量。如果不考虑各种边界最简单版是function deepClone(obj) { return JSON.parse(JSON.stringify(obj)); }但一旦面试官追问或者题目本身限定了“不能使用JSON方法”很多人就露馅了。JSON序列化深拷贝有一个著名的问题它不能处理undefined、Symbol、函数还会把Date变成字符串把RegExp变成空对象更没法处理循环引用。写深拷贝题时如果不考虑这些说明你根本没有在真实项目里写过数据处理。一个相对完整的深拷贝需要做到判断基础类型和null直接返回。对数组和对象递归遍历。遇到函数、Date、RegExp、Map、Set要单独处理。用WeakMap处理循环引用。我在试卷上写了一个简化但完整的版本function deepClone(obj, cache new WeakMap()) { if (obj null || typeof obj ! object) return obj; if (cache.has(obj)) return cache.get(obj); let clone Array.isArray(obj) ? [] : {}; cache.set(obj, clone); Object.keys(obj).forEach(key { clone[key] deepClone(obj[key], cache); }); return clone; }这里的关键设计是WeakMap它在缓存拷贝结果的同时不会泄漏内存。如果不用WeakMap用普通对象存储引用关系会在拷贝结束后残留大量引用导致内存无法回收。这个细节在2017年可能没多少人会主动关注但放在现在几乎成了深拷贝题的默认要求。4.3 防抖与节流面试官最爱的“工作场景题”防抖和节流是工程中极其常用的两个工具函数。同程的试卷上有一道题是设计一个输入框搜索功能要求用户停止输入300毫秒后才发起请求。这题的本质就是让写防抖函数。防抖的核心是事件触发后不立即执行而是设置一个计时器如果时间窗口内再次触发就重置计时器只有最后一次触发后等待完整的时间窗口才执行。function debounce(fn, delay) { let timer null; return function(...args) { if (timer) clearTimeout(timer); timer setTimeout(() { fn.apply(this, args); }, delay); }; }比较隐蔽的考点是this的绑定setTimeout 里的回调如果直接调用 fn那 fn 内部的 this 会指向 window严格模式下是undefined所以需要在外层把this存下来或者用箭头函数。以及要注意使用fn.apply(this, args)来保持this指向和参数传递。节流则是固定时间窗口内只执行一次。两者使用场景完全不同防抖适合搜索、输入框校验节流适合滚动监听、拖拽、resize。答题时如果能主动补充一句“防抖适用于最后一次操作有效节流适用于持续操作中需要规律执行”说明你是真的理解而不是背模板。常见误区笔试中很多人会纠结防抖节流的参数到底传不传事件对象。实际上工程里常用的是包裹一层debounce(handleSearch, 300) 这样用。事件对象如果需要要在包裹函数里接收后传进去保证原函数的签名不被破坏。5. 综合题与开放题这不是送分题是送命题很多考生以为开放题没有标准答案随便写写就能过。但实际上开放题恰恰是批卷人判断候选人在真实业务中能发挥多大价值的核心依据。因为笔试前面的题只能看出你会不会开放题才能看出你做过没做过、思考过没思考过。5.1 性能优化题从“闭眼背清单”到“按图索骥”同程这份试卷的综合题里有一道非常典型一个页面首屏加载很慢请分析可能的原因并给出优化方案。这类题非常开放但往往也是最容易暴露水平差距的地方。低分回答是背清单合并压缩资源、使用CDN、开启Gzip、图片懒加载然后结束。这不能说是错的但完全看不出你经历过什么。高分回答应该是一个有逻辑的分析框架第一步先明确瓶颈可能出在哪一层。网络层解析渲染层还是代码执行层第二步按优先级排查先看服务端响应时间TTFB再看资源体积和数量接着看是否阻塞渲染最后看JS执行是否吃满了主线程。第三步针对每一层给出具体方案比如提取首屏CSS、script加上defer或async、图片按需加载并采用现代格式、路由级代码分割。我当时的回答写法是以一个实际页面为例先描述页面现状有哪些模块、哪些资源再排一个时间轴标注出白屏时间、首屏时间、可交互时间的差异然后从“白屏时间为什么长”切入逐个定位原因。这种答法像是你真的做过一次性能优化分析而不是在背书。5.2 项目描述题一句话切中要害另一类开放题是“介绍一个你最满意的项目”。这道题看起来不用准备张嘴就能说但大多数人的答案是无效的他们花了三句话描述项目背景又花了三句话罗列技术栈唯独没有说清楚自己遇到了什么难点、解决了什么问题、产生了什么价值。正确的项目描述逻辑应该是按照“项目背景→我的职责→核心难点→解决思路→量化结果”这个顺序来组织。特别是在描述难点的时候不要讲那种“用了Vue所以开始学Vue”的成长型难点要讲技术难点比如“需要处理的数据量是10万条直接渲染会导致页面卡死所以做了虚拟列表将可视区外的节点移除最终把渲染时间从2秒降到200毫秒”。2017年那会儿前端面试还很吃“虚拟列表”“性能优化”“数据可视化”这些关键词如果你能在开放题中自然带出这些概念再把项目数据写得具体哪怕是你自己练手的项目也要有真实的性能指标批卷人就能从字里行间判断出你是一个有工程意识的人而不是一个只会跟着教程敲代码的人。6. 从备考到实战我在校招笔试中踩过的坑和总结的方法论如果你正在准备前端校招这份同程笔试卷可以直接当作模拟题来练。我在备考和实际应试的双重过程中积累了一些非常实用的经验也踩过不少坑整理出来供你参考。6.1 常见失分点复盘第一个失分点是时间分配失衡。有些人在CSS基础题上花了太多时间反复斟酌某一个小语法结果编程题只剩十几分钟写出来的代码全是半成品。编程题的分值往往更高而且半成品代码给阅卷人的感觉很不好。第二个失分点是只写结论不写过程。比如问到“什么是闭包”只写了“函数嵌套函数内部函数可以访问外部变量”。这个回答没有错但拿不到高分。优秀的回答应该带上使用场景、优缺点、以及内存管理上的注意事项。好的笔试答案应该像一篇微型技术文档而不是简答题的词条解释。第三个失分点是代码可用性差。有些人平时练习时依赖IDE和自动补全一到笔试就手写代码结构不完整连函数声明都没写全。这不是能力不够是练习方式错了。建议考前两周开始刻意练习手写代码逼自己不看IDE直接在草稿纸或者白板上写写完再誊抄到答题区。6.2 我总结的一套应试方法论首先拿到试卷前5分钟不要动笔先把整张卷子浏览一遍按分值和时间成本给题目分个优先级。通常我定的顺序是简单基础题快速过编程题先打完整草稿开放题最后集中火力写。其次遇到不会的题不要空着。即使只写出了你的思考过程也有可能拿到部分分。比如一道题需要用到Object.create但你只想到了new的方式把new方式完整写出来也比不写强。阅卷最怕看到空白最欢迎看到“虽然不完美但是有思考过程”。最后考前一定要对着往年真题做一次模拟考试限时、手写、不查资料。这个方法虽然朴素但真的非常有效。它能让你提前暴露出一堆问题写代码太慢、概念表述不清晰、开放题没有逻辑结构。等你把这些全部修正一遍真正上考场时心里就有底了。这套试卷虽然出自2017年但它考察的这些基础能力——布局原理、原型链、作用域、异步机制、手写代码依然是一个前端工程师的看家本事。现在很多人习惯了组件化开发拿到需求就直接引库反而忽略了底层这些“为什么”。所以如果要用一句话总结这份试卷的参考价值我想说的是框架年年换底子才是吃饭用的真家伙。打好这些基本功不管校招笔试怎么变你都能接得住。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门