年底了,又到了跳槽季啦,该刷题走起了。这里总结了一些被问到可能会懵逼的面试真题,有需要的可以看下~
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. 求代码输出值(函数/原型 指向问题)?
function Foo() {
getName = function () { console.log(1) }
return this
}
Foo.getName = function () { console.log(2) }
Foo.prototype.getName = function() { console.log(3) }
var getName = function () { console.log(4) }
function getName() {
console.log(5)
}
Foo.getName()
getName()
Foo().getName()
getName()
new Foo.getName()
new Foo().getName()
new new Foo().getName()
输出:
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
PS: 点击阅读原文即可到达~
最后
欢迎加我微信(winty230),拉你进技术群,长期交流学习...
欢迎关注「前端Q」,认真学前端,做个有专业的技术人...
在看点这里