阅读 5911

十几道含答案的大厂面试题总结

年底了,又到了跳槽季啦,该刷题走起了。这里总结了一些被问到可能会懵逼的面试真题,有需要的可以看下~

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属性对非替换内联元素无效,例如spancode

实现垂直居中

想要实现垂直方向的居中可以用绝对定位:

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 时,也会触发回流。原理是一样的,都为求一个“即时性”和“准确性”。
避免方式:
  1. 避免逐条改变样式,使用类名去合并样式
  2. 将 DOM “离线”,使用DocumentFragment
  3. 提升为合成层,如使用will-change
#divId {
  will-change: transform;
}
复制代码

优点

  • 合成层的位图,会交由 GPU 合成,比 CPU 处理要快
  • 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层
  • 对于 transform 和 opacity 效果,不会触发 layout 和 paint

注意:

部分浏览器缓存了一个 flush 队列,把我们触发的回流与重绘任务都塞进去,待到队列里的任务多起来、或者达到了一定的时间间隔,或者“不得已”的时候,再将这些任务一口气出队。但是当我们访问一些即使属性时,浏览器会为了获得此时此刻的、最准确的属性值,而提前将 flush 队列的任务出队。

12. 手写一个Promise?

答案太长,你可以参考这个issues:github.com/LuckyWinty/…

13. 谈谈web安全问题及解决方案

答案太长,你可以参考这个issues:github.com/LuckyWinty/…

14. HTTPS和HTTP有什么区别?

答案太长,你可以参考这个issues:github.com/LuckyWinty/…

15. Webpack性能优化你知道哪些?

答案太长,你可以参考这个issues:github.com/LuckyWinty/…

更多

本期汇总暂时到这里,更多题目,可以关注: github.com/LuckyWinty/…

博客关注:github.com/LuckyWinty/…

最后

  • 欢迎扫码加我,拉你进技术群,长期交流学习...
  • 欢迎关注「前端Q」,认真学前端,做个有专业的技术人...

GitHub