前端面试每日3+2(第29天)

12/30/2019 每日3+2

当你发现自己的才华撑不起你的野心时,就请安静下来学习吧!

鲁迅说过:答案仅供参考...

# 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)
1
2
3

缺点:回调地狱,不能用try catch捕获错误,不能return
回调地狱的问题在于:

  • 缺乏顺序性:回调函数导致调试困难,和大脑的思维方式不一致
  • 嵌套函数存在耦合性,一旦改动,就会牵一发动全身,即(控制反转)
  • 嵌套函数过多的话,很难处理错误
ajax('xxx1',()=>{
    // callback 函数题
    ajax('xxx2',()=>{
        // callback 函数体
        ajax('xxx3',()=>{
        // callback 函数体
        }) 
    })
})
1
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=>{
    // 操作逻辑
})
1
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()
1
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')
}
1
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
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);
});
1
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
1
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)

Last Updated: 1/14/2020, 7:56:38 AM
    asphyxia
    逆时针向