前端面试每日3+2(第29天)
当你发现自己的才华撑不起你的野心时,就请安静下来学习吧!
鲁迅说过:
答案仅供参考...
# 1、(携程)算法手写题
已知如下数组: var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]; 编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组
解析:
参考答案 (opens new window) --- 感谢【Daily-Interview-Question】 (opens new window)
# 2、(滴滴、挖财、微医、海康)JS 异步解决方案的发展历程以及优缺点。
解析:
# 1. 回调函数
setTimeout(()=>{
// callBack函数体
},1000)
2
3
缺点:回调地狱,不能用try catch捕获错误,不能return
回调地狱的问题在于:
- 缺乏顺序性:回调函数导致调试困难,和大脑的思维方式不一致
- 嵌套函数存在耦合性,一旦改动,就会牵一发动全身,即(控制反转)
- 嵌套函数过多的话,很难处理错误
ajax('xxx1',()=>{
// callback 函数题
ajax('xxx2',()=>{
// callback 函数体
ajax('xxx3',()=>{
// callback 函数体
})
})
})
2
3
4
5
6
7
8
9
优点:解决了同步的问题(只要有一个函数耗时过长,后面的任务就得排队等着,会拖延整个程序执行)
# 2. Promise
Promise就是为了解决callback的问题而产生的。
Promise实现了链式调用,每次调then是会返回一个全新的Promise,如果我们返回return,return的结果会被Rromise.resolve包装
优点解决了回调地狱的问题
ajax('xxx1')
.then(res=>{
return ajax('xxx2')
}).then(res=>{
return ajax('xxx3')
}).then(res=>{
// 操作逻辑
})
2
3
4
5
6
7
8
缺点:无法取消Promise(abort),错误需要通过回调捕获
# 3. Generator
特点:可以控制函数的执行,配合co函数库使用
function *fetch() {
yield ajax('XXX1', () => {})
yield ajax('XXX2', () => {})
yield ajax('XXX3', () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
2
3
4
5
6
7
8
9
# 4. Async/await
async、await是异步的终极解决方案
优点:代码清晰,不像Promise写一堆then,处理了回调地狱的问题
缺点:await函数将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用await会导致性能上的降低
async function test() {
// 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
// 如果有依赖性的话,其实就是解决回调地狱的例子了
await fetch('XXX1')
await fetch('XXX2')
await fetch('XXX3')
}
2
3
4
5
6
7
下面来看一个使用 await 的例子:
let a = 0
let b = async () => {
a = a + await 10
console.log('2', a) // -> '2' 10
}
b()
a++
console.log('1', a) // -> '1' 1
2
3
4
5
6
7
8
# 5. 事件监听、发布/订阅
可以参考阮一峰异步编程 (opens new window)
参考答案 (opens new window) --- 感谢【Daily-Interview-Question】 (opens new window)
# 3、(微医)Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?
解析:
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve(5);
console.log(2);
}).then(val => {
console.log(val);
});
promise.then(() => {
console.log(3);
});
console.log(4);
setTimeout(function() {
console.log(6);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
执行结果: 124536
promise构造函数是同步执行的,then方法是异步执行的
参考答案 (opens new window) --- 感谢【Daily-Interview-Question】 (opens new window)
# 4、(兑吧)如何实现一个 new
解析:
// 实现一个new
var Dog = function(name) {
this.name = name
}
Dog.prototype.bark = function() {
console.log('wangwang')
}
Dog.prototype.sayName = function() {
console.log('my name is ' + this.name)
}
let sanmao = new Dog('三毛')
sanmao.sayName()
sanmao.bark()
// new 的作用
// 创建一个新对象obj
// 把obj的__proto__指向Dog.prototype 实现继承
// 执行构造函数,传递参数,改变this指向 Dog.call(obj, ...args)
// 最后把obj赋值给sanmao
// 根据规范,返回 null 和 undefined 不处理,依然返回obj
function _new(fn, ...arg) {
const obj = Object.create(fn.prototype);
const ret = fn.apply(obj, arg);
return ret instanceof Object ? ret : obj;
}
var simao = _new(Dog, 'simao')
simao.bark()
simao.sayName()
console.log(simao instanceof Dog) // true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
TODO: 看一下JS高程中对new的定义
参考答案 (opens new window) --- 感谢【Daily-Interview-Question】 (opens new window)
# 5、(网易)简单讲解一下http2的多路复用。
解析:
在 HTTP/1 中,每次请求都会建立一次HTTP连接,也就是我们常说的3次握手4次挥手,这个过程在一次请求过程中占用了相当长的时间,即使开启了 Keep-Alive ,解决了多次连接的问题,但是依然有两个效率上的问题:
- 第一个:串行的文件传输。当请求a文件时,b文件只能等待,等待a连接到服务器、服务器处理文件、服务器返回文件,这三个步骤。我们假设这三步用时都是1秒,那么a文件用时为3秒,b文件传输完成用时为6秒,依此类推。(注:此项计算有一个前提条件,就是浏览器和服务器是单通道传输)
- 第二个:连接数过多。我们假设Apache设置了最大并发数为300,因为浏览器限制,浏览器发起的最大请求数为6,也就是服务器能承载的最高并发为50,当第51个人访问时,就需要等待前面某个请求处理完成。
HTTP/2的多路复用就是为了解决上述的两个性能问题。 在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream)。 帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。 多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。 解析:
参考答案 (opens new window) --- 感谢【Daily-Interview-Question】 (opens new window)