前端十几道含答案的大厂面试题总结
winty
年底了,又到了跳槽季啦,该刷题走起了。这里总结了一些被问到可能会懵逼的面试真题,有需要的可以看下~
1. 说说JavaScript中有哪些异步编程方式?
1. 回调函数
f1(f2);
回调函数是异步编程的基本方法。其优点是易编写、易理解和易部署;缺点是不利于代码的阅读和维护,各个部分之间高度耦合 (Coupling),流程比较混乱,而且每个任务只能指定一个回调函数。
2. 事件监听
f1.on('done',f2);
事件监听即采用事件驱动模式,任务的执行不取决于代码的顺序,而取决于某个事件是否发生。其优点是易理解,可以绑定多个事件,每个事件可以指定多个回调函数,可以去耦合, 有利于实现模块化;缺点是整个程序都要变成事件驱动型,运行流程会变得不清晰。
3. 发布/订阅
f1: jQuery.publish("done"); f2: jQuery.subscribe("done", f2);
假定存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行,这就叫做 "发布/订阅模式" (publish-subscribe pattern),又称 "观察者模式" (observer pattern)。该 方法的性质与"事件监听"类似,但其优势在于可以 通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
4. promise对象
f1().then(f2);
Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供 统一接口 ;思想是, 每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。其优点是回调函数是链式写法,程序的流程非常清晰,而且有一整套的配套方法, 可以实现许多强大的功能,如指定多个回调函数、指定发生错误时的回调函数, 如果一个任务已经完成,再添加回调函数,该回调函数会立即执行,所以不用担心是否错过了某个事件或信号;缺点就是编写和理解相对比较难。
2. 有哪些监控网页卡顿的方法?
卡顿
网页的 FPS
网页内容在不断变化之中,网页的 FPS 是只浏览器在渲染这些变化时的帧率。帧率越高,用户感觉网页越流畅,反之则会感觉卡顿。
监控卡顿方法
每秒中计算一次网页的 FPS 值,获得一列数据,然后分析。通俗地解释就是,通过 requestAnimationFrame API 来定时执行一些 JS 代码,如果浏览器卡顿,无法很好地保证渲染的频率,1s 中 frame 无法达到 60 帧,即可间接地反映浏览器的渲染帧率。
var lastTime = performance.now(); var frame = 0; var lastFameTime = performance.now(); var loop = function(time) { var now = performance.now(); var fs = (now - lastFameTime); lastFameTime = now; var fps = Math.round(1000/fs); frame++; if (now > 1000 + lastTime) { var fps = Math.round( ( frame * 1000 ) / ( now - lastTime ) ); frame = 0; lastTime = now; }; window.requestAnimationFrame(loop); }
我们可以定义一些边界值,比如连续出现3个低于20的 FPS 即可认为网页存在卡顿。
3. 说说script 标签中的defer 和 async 异同点?
defer
这个属性的用途是表明脚本在执行时不会影响页面的构造。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。因此,在script元素中设置defer属性,相当于告诉浏览器立即下载,但延迟执行。
HTML5规范要求脚本按照它们出现的先后顺序执行,因此第一个延迟脚本会先于第二个延迟脚本执行,而这两个脚本会先于DOMContentLoaded事件执行。在现实当中,延迟脚本并不一定会按照顺序执行,也不一定会在DOMContentLoad时间触发前执行,因此最好只包含一个延迟脚本。
对于不支持的浏览器,如safari,并不会延迟执行,还是会阻塞浏览器渲染。
async
这个属性与defer类似,都用于改变处理脚本的行为。同样与defer类似,async只适用于外部脚本文件,并告诉浏览器立即下载文件。但与defer不同的是,标记为async的脚本并不保证按照它们的先后顺序执行。
第二个脚本文件可能会在第一个脚本文件之前执行。因此确保两者之间互不依赖非常重要。指定async属性的目的是不让页面等待两个脚本下载和执行,从而异步加载页面其他内容。
总结
async 和 defer 虽然都是异步的,不过还有一些差异,使用 async 标志的脚本文件一旦加载完成,会立即执行;而使用了 defer 标记的脚本文件,需要在 DOMContentLoaded 事件之前执行。
可以再扩展一下:link preload也可以用于js提前加载,和上述两者的区别? 还有应用场景上的建议
4. margin:auto 为什么可以实现垂直居中?
margin概念:
margin属性为给定元素设置所有四个(上下左右)方向的外边距属性。这是四个外边距属性设置的简写。四个外边距属性设置分别是: margin-top,margin-right,margin-bottom和margin-left。指定的外边距允许为负数。
margin的top和bottom属性对非替换内联元素无效,例如span和 code。
实现垂直居中
想要实现垂直方向的居中可以用绝对定位:
div { width: 20px; height: 20px; position: absolute; top: 0; left: 0; right: 0; bottom: 0; margin: auto; }
为什么能实现垂直居中
块状水平元素,如div元素(下同),在默认情况下(非浮动、绝对定位等),水平方向会自动填满外部的容器;如果有margin-left/margin-right,padding-left/padding-right,border-left-width/border-right-width等,实际内容区域会响应变窄。
但是,当一个绝对定位元素,其对立定位方向属性同时有具体定位数值的时候,流体特性就发生了。具有流体特性绝对定位元素的margin:auto的填充规则和普通流体元素一模一样,含有以下特性:
- 如果一侧定值,一侧auto,auto为剩余空间大小;
- 如果两侧均是auto, 则平分剩余空间
5. Cdn有哪些优化静态资源加载速度的方法?
可以参考阿里云团队的《CDN之我见》。总结如下:
资源调度:CDN会根据用户接入网络的ip寻找距离用户最优路径的服务器。调度的方式主要有DNS调度、http 302调度、使用 HTTP 进行的 DNS 调度(多用于移动端); 缓存策略和数据检索:CDN服务器使用高效的算法和数据结构,快速的检索资源和更新读取缓存; 网络优化:从OSI七层模型进行优化,达到网络优化的目的。 L1物理层:硬件设备升级提高速度 L2数据链路层:寻找更快的网络节点、确保 Lastmile 尽量短 L3路由层:路径优化,寻找两点间最优路径 L4传输层:协议TCP优化,保持长连接、TCP快速打开 L7应用层:静态资源压缩、请求合并
6. fetch 和 ajax 的区别?
Ajax 技术的核心是XMLHttpRequest 对象(简称XHR)。 XHR 为向服务器发送请求和解析服务器响应提供了流畅的接口。能够以异步方式从服务器取得更多信息,意味着用户单击后,可以不必刷新页面也能取得新数据。 看一个调用例子:
var xhr = createXHR(); xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText); } else { alert("Request was unsuccessful: " + xhr.status); } } }; xhr.open("get", "example.txt", true); xhr.send(null);
fetch号称是ajax的替代品,它的API是基于Promise设计的,旧版本的浏览器不支持Promise,需要使用polyfill es6-promise,举个例子:
// 原生XHR var xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { console.log(xhr.responseText) // 从服务器获取数据 } } xhr.send() // fetch fetch(url) .then(response => { if (response.ok) { return response.json(); } }) .then(data => console.log(data)) .catch(err => console.log(err))
看起来好像是方便点,then链就像之前熟悉的callback。 在MDN上,讲到它跟jquery ajax的区别,这也是fetch很奇怪的地方:
当接收到一个代表错误的 HTTP 状态码时,从 fetch()返回的 Promise 不会被标记为 reject, 即使该 HTTP 响应的状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ), 仅当网络故障时或请求被阻止时,才会标记为 reject。 默认情况下, fetch 不会从服务端发送或接收任何 cookies, 如果站点依赖于用户 session,则会导致未经认证的请求(要发送 cookies,必须设置 credentials 选项).
7. 求代码输出值(函数/原型 指向问题)?
2
函数也是一个 object, 可以拥有属性和方法, Foo.getName 被赋值为一个输出 2 的函数, 所以输出 2
Foo.getName = function () { console.log(2) } Foo.getName() // 输出 2
4
getName() 父级作用域为 window, 相当于调用 window.getName(), Foo() 还未被执行, 不需要考虑 Foo 函数体内对 getName 的影响, 剩下最后两个
var getName = function () { console.log(4) } // 函数表达式 function getName() { // 函数声明 console.log(5) }
函数声明会提升, 也就是即使在后边声明的函数, 在前别也能调用, 如:
getName() // 输出 5 function getName() { // 函数声明 console.log(5) }
但题中函数表达式会覆盖函数声明, 来看下边这段:
getName() // 输出 5, 函数声明提升的结果 var getName = function () { console.log(4) } // 函数表达式 getName() // 输出 4, 函数声明提升后, 又被函数表达式覆盖了 function getName() { // 函数声明 console.log(5) } getName() // 还是输出 4, 覆盖的结果, 函数声明提升了, 不按照文本的顺序重新声明的, 你可以想象它被移到了最前边
1
Foo().getName()
我们一步步拆解, 上边的语句相当于
var context = Foo(); context.getName();
来看 Foo 的声明:
function Foo() { // 下边这句没有 var, let 或 const, 相当于 window.getName = xxx getName = function () { console.log(1) } return this // 这里的 this 要看调用方式, 直接调用 Foo() 则 this 指向 window, new 调用, this 指向 new 出来的实例 }
仔细看上边的注释, Foo 函数体内对 window.getName 进行了改写, 这是下一个输出的关键
1
如上边分析的, Foo() 函数的执行, 对 window.getName 进行了改写, window.getName 此时已经变为 function () { console.log(1) }
2
new Foo.getName()
该语句先执行 Foo.getName, 与第一个结论一致, 输出 2, 只是 new 会返回一个 object, 这个 object 指向 new 出来的实例, 但这里这个实例没被使用, 就不进一步分析了
3
new Foo().getName()
拆解如下
var foo = new Foo() foo.getName()
如果你是一路看分析下来的, 就会明白 foo 这个实例, 就是 Foo 函数体里的 this. 从原型的知识中, 我们可以知道, 如果调用一个实例的方法, 在实例方法中找不到, 就会从实例原型中找.
也就是会找到下边这个方法, 并执行:
Foo.prototype.getName = function() { console.log(3) }
3
new new Foo().getName()
拆解如下
var foo = new Foo(); var bar = new foo.getName();
从上边 new Foo().getName() 的分析, 可以知道 foo.getName() 是在 foo 的原型里边的, 这里 new 了一下原型里边的函数, 相当于先执行了, 再返回了一个新的实例. 这里的执行, 也就是执行了下边这个方法:
Foo.prototype.getName = function() { console.log(3) }
只是 new 额外返回一个实例, 实例没被使用, 没什么特殊的.
如果还有不明白的地方, 建议阅读 《JavaScript 语言精髓》第四章 (本人看的是修订版) 关于函数调用相关的内容. 相关电子书可以微信关注公众号 前端Q, 回复 ebook 阅读. 此外, 建议购买正版.
8. 如何判断左右小括号是否全部匹配。如 ( ( ))()((((()))))?
/** * @param {string} s * @return {boolean} */ var isValid = function(s) { var stack = [] var map = { '(' : ')', '[': ']', '{': '}' } for (var char of s) { if(char in map) { stack.push(char) } else { if( !stack.length || char != map[stack.pop()]) { return false } } } // 如果最后stack 里没有元素了, 就一定是匹配的 return !stack.length };
9. 用css画一个扇形?
width: 0; height: 0; border: solid 100px red; border-color: red transparent transparent transparent; border-radius: 100px;
10. 用css实现已知或者未知宽度的垂直水平居中?
/ / 1 .wraper { position: relative; .box { position: absolute; top: 50%; left: 50%; width: 100px; height: 100px; margin: -50px 0 0 -50px; } } // 2 .wraper { position: relative; .box { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } } // 3 .wraper { .box { display: flex; justify-content:center; align-items: center; height: 100px; } } // 4 .wraper { display: table; .box { display: table-cell; vertical-align: middle; } } //5 .container { display: grid; grid-auto-columns: 1fr; grid-auto-rows: 200px; background: #eee; } .parent { background: grey; justify-self: center; align-self: center; } .child { font-size: 30px; } //6、块级元素:calc() .parent { width: 300px; height: 300px; border: 1px solid red; position: relative; } .child { width: 100px; height: 100px; background: blue; padding: -webkit-calc((100% - 100px) / 2); padding: -moz-calc((100% - 100px) / 2); padding: -ms-calc((100% - 100px) / 2); padding: calc((100% - 100px) / 2); background-clip: content-box; } //7、margin:auto实现绝对定位元素的居中 .element { width: 600px; height: 400px; position: absolute; left: 0; top: 0; right: 0; bottom: 0; margin: auto; /* 有了这个就自动居中了 */ } //8、 .parent{ display: flex; } .parent{ display: flex; width: 500px; height: 500px; background-color: pink; } .child{ flex: 0 0 auto; margin: auto; width: 100px; height: 100px; background-color: red;
11. 如何理解回流和重绘? ?
回流:当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)。
重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。这个过程叫做重绘。 由此我们可以看出,重绘不一定导致回流,回流一定会导致重绘。
常见的会导致回流的元素:
- 常见的几何属性有 width、height、padding、margin、left、top、border 等等。
- 最容易被忽略的操作:获取一些需要通过即时计算得到的属性,当你要用到像这样的属性:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight 时,浏览器为了获取这些值,也会进行回流。
- 当我们调用了 getComputedStyle 方法,或者 IE 里的 currentStyle 时,也会触发回流。原理是一样的,都为求一个“即时性”和“准确性”。
避免方式:
- 避免逐条改变样式,使用类名去合并样式
- 将 DOM “离线”,使用DocumentFragment
- 提升为合成层,如使用will-change
#divId { will-change: transform; }
优点
- 合成层的位图,会交由 GPU 合成,比 CPU 处理要快
- 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层
- 对于 transform 和 opacity 效果,不会触发 layout 和 paint
注意:
部分浏览器缓存了一个 flush 队列,把我们触发的回流与重绘任务都塞进去,待到队列里的任务多起来、或者达到了一定的时间间隔,或者“不得已”的时候,再将这些任务一口气出队。但是当我们访问一些即使属性时,浏览器会为了获得此时此刻的、最准确的属性值,而提前将 flush 队列的任务出队。
12. 手写一个Promise?
答案太长,你可以参考这个issues:https://github.com/LuckyWinty/fe-weekly-questions/issues/20
13. 谈谈web安全问题及解决方案
答案太长,你可以参考这个issues:https://github.com/LuckyWinty/fe-weekly-questions/issues/1
14. HTTPS和HTTP有什么区别?
答案太长,你可以参考这个issues:https://github.com/LuckyWinty/fe-weekly-questions/issues/2
15. Webpack性能优化你知道哪些?
答案太长,你可以参考这个issues:https://github.com/LuckyWinty/fe-weekly-questions/issues/4
更多
本期汇总暂时到这里,更多题目,可以关注: https://github.com/LuckyWinty/fe-weekly-questions
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。