ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

2015前端笔试题复盘:闭包、原型链与性能优化核心考点

2015前端笔试题复盘:闭包、原型链与性能优化核心考点 2. 这套卷子究竟在考核什么最近整理旧笔记本里的资料翻出来一份当年保存的人人网2015研发笔试卷E前端方向。看着上面密密麻麻的批注忽然觉得这套题放到现在复盘一下依然挺有嚼头。虽然已经是好几年前的东西但里面涉及的核心知识点——JavaScript 闭包、原型链、浏览器兼容、页面性能优化——恰好是每一代前端开发者都必须跨过去的坎。如果你正在准备面试或者想检验一下自己的基础功底这套卷子值得认真做一遍。先说清楚这张卷子的背景。2015年的人人网正处于移动端浪潮的转型期Web 端依然承担着大量用户交互场景比如状态发布、相册上传、好友动态。笔试试卷分成通用技术基础和方向专项两块既考验在校学生的知识面也筛选扎实的前端基础能力。当时没有现在这么多框架轮子笔试题大多围绕原生 JavaScript、浏览器机制、网络协议来出反而更能看出一个人的内功深浅。1. 试卷的整体结构与考察逻辑1.1 2015年前端笔试为什么难现在回想起来2015年是一个很微妙的时间点。jQuery 还是绝大多数项目的标配AngularJS 1.x 在企业里开始流行React 刚刚进入国内技术圈的视野ES6 虽然已经定稿但还没有大规模普及。这种技术交替期有一个特点面试官知道新东西的出现是趋势但团队里线上跑的还是老代码所以笔试题目会同时覆盖“传统基本功”和“新方向敏感度”两条线。传统基本功的考察集中在三块JavaScript 的语言特性、浏览器兼容性处理、页面性能优化。而新方向敏感度通常只会作为附加题出现比如“是否了解 ES6 的新特性”“对组件化开发有什么理解”占分不多但能拉开差距。人人网这套卷子也遵循了这个规律整张卷面大约三分之二是基础题三分之一是开放性设计题。1.2 卷面模块分布与分值逻辑从题型分布来看常见的结构是这样的模块类型占比考察目标HTML/CSS填空 简答20%布局基本功、语义化理解JavaScript 基础写结果 代码补全40%语言特性掌握程度浏览器/网络基础简答15%页面加载全链路理解性能优化简答 方案设计15%实战经验算法逻辑编程题10%基础算法能力这里要特别提一下 JavaScript 部分为什么占这么大比重。2015年还没有 TypeScript也没有现在这么完善的组件生态前端交互全靠手写 JS 撑着。一个候选人如果连闭包、原型、this 指向都说不清楚后续培养成本会非常高。所以笔试卷里 JS 的占比高本质上是在筛选“能直接上手写交互的人”。2. JavaScript 闭包与作用域这张卷子的核心难点2.1 经典闭包题目循环里绑定事件当年的笔试题里出现频率最高的一道题长这样function bindClick() { var list document.querySelectorAll(li); for (var i 0; i list.length; i) { list[i].onclick function() { console.log(i); }; } }问点击第 3 个 li 时控制台输出什么答案是 3如果列表长度足够而不是 0、1、2。很多人当时第一眼会以为输出的是当前索引但实际运行结果会让不少人懵一下。原因在于var i声明在函数作用域内循环结束后i已经变成了最终的列表长度值。所有点击回调函数引用的是同一个i变量当事件真正触发时循环早就跑完了取到的自然是最终值。要修复这个问题当时的常规方案是利用 IIFE 创建独立作用域for (var i 0; i list.length; i) { (function(index) { list[index].onclick function() { console.log(index); }; })(i); }如果放在 2015 年能写出这个 IIFE 版本已经算合格。如果还能补一句“用 ES6 的 let 声明也是可以的”面试官会高看一眼。因为let在每个循环迭代中都会创建一个块级作用域绑定这个问题从语言层面被直接解决了。2.2 this 指向与作用域链的综合题除了闭包这张卷子还喜欢考察this的指向。有一个经典题目几乎每个版本都会出现var name window; var obj { name: obj, getName: function() { return function() { return this.name; }; } }; console.log(obj.getName()());答案是window。因为obj.getName()执行后返回了一个普通函数这个函数在执行时并没有被 obj 对象调用而是孤立的调用所以this指向全局对象。在浏览器环境里全局对象的name属性恰好就是window字符串。这道题其实是在考一个关键概念函数的this取决于调用方式而不是定义位置。很多人把this和作用域链搞混以为函数定义在 obj 内部this就应该指向 obj这是典型的误解。作用域链是词法层面的而this是运行时绑定的两者完全是两码事。如果题目再加一个变体问如何让返回值里的this指向 obj解法就有三种在外层函数里缓存var that this用bind绑定或者用箭头函数。这三种写法在 2015 年的卷子上都能拿到分但得分点不一样——能说出箭头函数的说明对 ES6 有主动了解。2.3 原型链手写一个继承当时还有一个高频考点是原型链继承。一个典型题目是function Animal(name) { this.name name; } Animal.prototype.sayName function() { console.log(this.name); };要求写一个 Cat 类继承 Animal并且 Cat 的实例能调用sayName方法。如果只知道默认的原型继承写法这道题只能得一半分因为很多人会写出这样的代码function Cat(name) { Animal.call(this, name); } Cat.prototype new Animal();这写法有个问题new Animal()会生成一个带name属性此时是 undefined的实例然后把它当作 Cat 的原型。如果 Animal 的构造函数内部有副作用或者需要参数初始化这个调用会造成不必要的执行。更规范的做法是Cat.prototype Object.create(Animal.prototype)然后修正Cat.prototype.constructor Cat。为什么要把constructor指回来这是一个细节考点。因为经过原型重写后Cat.prototype变成了一个全新的对象它的constructor默认指向Object而不是Cat如果不手动修正instanceof的结果不会受影响但cat.constructor Cat会变成 false这在某些依赖constructor做类型判断的代码里会出问题。3. 浏览器兼容与 DOM 操作那年的基本功3.1 事件处理的兼容差异2015 年的笔试是不敢绕开浏览器兼容的因为当时 IE6/7/8 虽然已经呈下降趋势但 IE8 在部分企业环境里依然活跃。人人网的用户群体里IE 用户占比远高于现在所以前端面试题里必然会出现事件兼容的题目。一个典型题目是如何兼容 IE 和标准浏览器绑定事件function addEvent(elem, type, handler) { if (elem.addEventListener) { elem.addEventListener(type, handler, false); } else if (elem.attachEvent) { elem.attachEvent(on type, handler); } else { elem[on type] handler; } }关键考点有四个一是attachEvent的on前缀不能漏二是addEventListener的第三个参数false表示冒泡阶段触发当时的主流实践是挂冒泡而不是捕获三是attachEvent里this指向window而不是当前元素这差异很隐蔽四是addEventListener可以给同一元素重复绑定多个相同事件而onclick赋值会覆盖前一个。顺带还要提一个比较高频的题目如何阻止冒泡和取消默认行为。标准写法是e.stopPropagation()和e.preventDefault()IE 下则是e.cancelBubble true和e.returnValue false。一道小简答题能答全细节的人不多因为大部分人只会记标准写法对 IE 的写法不熟悉。3.2 封装一个兼容的 XMLHttpRequest除了事件DOM 操作里还有一个常考的点是 Ajax 封装。当时已经开始用 jQuery 的$.ajax但笔试想要的是让你不依赖库实现一个兼容的请求对象。function getXHR() { if (window.XMLHttpRequest) { return new XMLHttpRequest(); } else { return new ActiveXObject(Microsoft.XMLHTTP); } } function ajaxGet(url, callback) { var xhr getXHR(); xhr.open(GET, url, true); xhr.onreadystatechange function() { if (xhr.readyState 4) { if (xhr.status 200) { callback(xhr.responseText); } } }; xhr.send(null); }这里还藏着一个考点readyState 4表示请求完成但 HTTP 状态码是 200 才代表成功。有些人会把 304走缓存也当成成功处理如果题目要求考虑缓存场景通常还要判断xhr.status 200 || xhr.status 304。不过从实际产品角度304 后浏览器往往已经帮你取到了缓存内容responseText 里可能拿到的是空字符串所以不能简单把它等同于请求成功。3.3 获取元素位置的兼容处理还有一道让我印象很深的题要求写出获取元素在页面上绝对位置的函数。核心是offsetTop和offsetParent的循环累加但要处理边界情况function getAbsoluteTop(element) { var top 0; while (element) { top element.offsetTop; element element.offsetParent; } return top; }考点在于offsetParent可能为 null所以循环条件要用element而不是element.offsetParentoffsetTop是相对offsetParent的位置不是相对 document还要注意position: fixed的元素offsetParent行为在不同浏览器下不一致。这题不算难但能把边界条件考虑完整的人并不多。4. 性能优化考点笔试里的送分题与陷阱区4.1 页面加载性能的经典考察人人网这类内容型产品页面首屏加载性能直接影响用户留存。笔试卷里有一道几乎必考的题请列出你能想到的网页性能优化方案。这道题看着是送分题其实分水岭很大。基础差的同学会写出“压缩 JS/CSS”“合并图片”“用 CDN”这三板斧但这只能拿基础分。高分答案通常还要覆盖以下几个方面减少 HTTP 请求次数合并脚本、CSS Sprites、内联小图资源加载策略脚本放底部、样式放头部、按需加载非关键资源缓存策略静态资源带版本号利用浏览器缓存渲染层面减少 DOM 操作、避免强制同步重排服务端优化开启 gzip、设置合理的响应头如果能在答案里写一句“对首屏非关键图片做懒加载”面试官会觉得你有实际项目经验因为这已经是 2015 年比较新的实践了。4.2 重排与重绘区分关键概念有一道追问很常见什么是重排reflow和重绘repaint哪些操作会引起重排这题考察的是对浏览器渲染机制的理解。把 JS 代码风格融入答案会更有竞争力读offsetWidth、scrollTop、clientTop时如果前面修改过样式但还没触发渲染浏览器会强制同步重新计算布局这是最容易引发性能问题的操作之一。解决思路是把读操作一次性收集起来或者用requestAnimationFrame把写入延迟到下一次渲染帧。当时的笔试卷上能准确说出“读取布局属性会导致强制同步布局”的人非常少因为大家只停留在“重排就是重新计算位置重绘就是重新画”这种表面理解。如果有余力可以在答题时补充一个例子var width el.offsetWidth; // 读 el.style.width width 10 px; // 写这种连续读写会造成多次布局抖动高绩效答案会把读写分离。这部分虽然偏实战但在笔试里简单提到就能加分。4.3 懒加载的实现思路卷子最后的信息题里有一道问如何实现图片懒加载这里不需要写出完整代码但需要给出一套可落地的思路。常规做法是监听scroll事件判断图片是否进入视口一旦进入就把>function check() { var img document.querySelector(img[data-src]); if (!img) return; var rect img.getBoundingClientRect(); if (rect.top window.innerHeight rect.bottom 0) { img.src img.getAttribute(data-src); img.removeAttribute(data-src); } } window.addEventListener(scroll, check);这里有一个容易被忽视的点getBoundingClientRect返回的是相对视口的位置所以判断条件只需要比较rect.top是否小于视口高度即可不用手动加scrollTop。很多人会搞混这个坐标系写出来的判断条件复杂而且容易错。5. 网络与缓存基础一道简答题背后的知识体系5.1 HTTP 状态码的掌握程度网络基础在当年的笔试卷里不算大头但一定会出现。常见的题目是请列举 200、301、302、304、403、404、500、503 分别代表什么。这道题的难点在于 301 和 302 的区别以及 304 的缓存机制。301 是永久重定向浏览器会缓存重定向结果后续请求直接跳到新地址302 是临时重定向每次请求还是先访问原地址。真实项目中301 常用于域名迁移302 常用于未登录跳转。能答出这个场景区别说明不只是背状态码而是理解过实际业务。304 这个状态码也很容易答偏。304 表示的是“服务端资源未修改可以用缓存副本”不是一种错误。整个交互过程是客户端请求资源时带上If-Modified-Since或If-None-Match服务端检查后发现资源没有变更就返回 304 和空响应体客户端接着用本地缓存。这可以减少不必要的传输。很多人把 304 和“请求失败”画等号这在缓存面试题里是明显扣分点。5.2 浏览器缓存的几个响应头关于浏览器缓存还有一道简答题说说Expires和Cache-Control和ETag的区别。Expires是 HTTP/1.0 时代的产物指定一个绝对的过期时间但客户端时间和服务器时间不一致时会出问题。Cache-Control是 HTTP/1.1 的标准max-age指定相对存活时间更可靠。ETag是实体标签比较内容是否变化配合If-None-Match使用。强缓存和协商缓存的先后顺序应该是先看Cache-Control/Expires没命中再看ETag/Last-Modified。为了讲清楚我发现一个生活类比很好用强缓存就像是冰箱里的酸奶上面写着保质期保质期内你直接喝就行不用问商家协商缓存像是你拿着旧照片去问商家“这个商品是不是还是这样”商家说没变你再继续用。这道题能拿高分的同学通常还会主动提一嘴Last-Modified的精度是秒级如果资源在 1 秒内被修改两次就检测不到变化所以ETag更可靠。这种小细节不是背题能背出来的。6. 算法逻辑与开放设计题从笔试卷看思维格局6.1 字符串处理类的简单算法题2015年的前端笔试题对算法要求不算高通常是一两道数组/字符串处理的基础题。最常见的题目是写一个函数判断字符串是否为回文。function isPalindrome(str) { str str.replace(/[^0-9a-zA-Z]/g, ).toLowerCase(); return str str.split().reverse().join(); }考察点是字符串的常用方法组合正则去除非字母数字、转为小写、分割反转再拼接。还有一种写法是用双指针从两端向中间比较复杂度是 O(n)但笔试实际考察的主要是基本功是否熟练而不是复杂度优化。还有一道字符串去重的题目也出现过function unique(str) { var result ; for (var i 0; i str.length; i) { if (result.indexOf(str[i]) -1) { result str[i]; } } return result; }如果能写出indexOf已经可以如果写一个Object作为哈希表去重那就更靠近高级答案。这说明你对 JavaScript 数据结构有实际应用能力而不仅是会用 API。6.2 组件设计题怎么设计一个轮播图开放设计题在整个卷子里最有区分度。常见的一道题请设计一个图片轮播组件的 API 和核心逻辑要求支持自动播放、循环播放、点击切换。这道题不要求写完整代码但要求描述清楚结构。我当时给出的思路是对外暴露init、goTo、prev、next、play、stop方法内部用索引currentIndex维护当前状态用setInterval实现自动播放切换时计算偏移量并设置容器的transform或left值。更高质量的答案会补充两个设计细节一是循环播放时的无缝衔接怎么做第一张前面克隆最后一张或者到边界时直接跳转但带过渡动画二是自动播放时鼠标 hover 要暂停离开后恢复三是如果用户快速点击多次要防止动画堆积最好做节流或重置定时器。能主动想到这些边界情况的人在项目里大概率是一个靠谱的开发者。7. 复盘这份卷子给今天的前端开发者留下了什么7.1 2015年与现在的考点变化现在回头看这份卷子有些知识点已经成了历史比如attachEvent没有人在新项目里会用到但更多内容只是换了一副面孔出现。闭包依然是面试高频题只是提问方式变成了“这段 React 代码为什么拿不到最新 state”或“这个闭包内存泄漏怎么排查”。性能优化还是必考只是从“合并静态资源”升级成了“分析 LCP、FCP、CLS”。我最大的感受是通识知识的折旧速度比想象中慢。当年花了很久搞懂的this指向问题在 React 类组件里依然让人踩坑当年在笔试题里画的原型链在封装高阶组件时照样用得上。反过来说那些只背 API 而不理解原理的候选人到了今天这个框架生态里依然很难适应。7.2 准备笔试的三个建议结合当年备考和带新人的经验我给准备研发笔试的同学三个建议。第一把 JS 的语言机制吃透不要只刷 API。闭包、作用域链、原型链、事件的运行机制这些是前端面试的“母题”任何框架题背后都逃不开这些。第二做笔试题时不要把思路写得太跳跃面试官看的是你的推导过程代码写不出来哪怕用文字把解决思路写清楚也有分。第三考前了解一下目标公司的主流技术栈在开放题里体现出你对该技术方向的了解而不是空谈概念。8. 关于这套笔试卷的一点点后记我记得当时做完这套卷子之后还特意去找了几家同学对答案。有人觉得这些题目太“基础”和实际项目脱节但后来工作几年再回头看这些基础其实是所有业务的底层地基。直到现在我面试候选人时依然会不自觉地用闭包、this、事件代理这几个问题来试探对方的 JS 功底无论对方简历上写了多少个框架。如果你正打算投递前端岗位的研发校招不妨把这套卷子的知识点过一遍。不用背答案而是要把每个问题背后的原理搞清楚尤其是“为什么”这个层面理解了面试官问什么变体你都不慌。
返回列表