# 20. 头条面试

# 1. CDN 是什么?怎么实现缓存?

CDN是内容分发网络,CDN是构建在现有网络基础之上的智能虚拟网络。CDN的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。 其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度。

https://kb.cnblogs.com/page/121664/

https://blog.csdn.net/galen2016/article/details/81674247

# 2. HTTP 缓存有哪些实现方式?

HTTP缓存是浏览器缓存,缓存都保存在Disk Cache和Memory Cache中。不管是强缓存还是协商缓存都从这两种缓存中获取。

普通刷新:优先使用memory cache其次是disk cache。强缓存无效Expires/Cache-Control无效,协商缓存Last-Modified/Etag有效。

强制刷新(ctrl+F5):浏览器不使用缓存,强缓存和协商缓存都无效。

地址栏输入地址:查找disk cache

# 强缓存,如何实现?

Expires/Cache-Control控制。如果发送请求的时间在Expires或者Cache-Control之前,那么本地缓存始终有效,否则就会发送请求到服务器来获取资源,状态码为200。

# 协商缓存,如何实现?

Last-Modified(响应头)/If-Modified-Since(请求头)控制或者ETag(响应头)/If-None-Match(请求头)控制。当没有强缓存时,会向服务端寻求帮助,也就是问一下服务端有没有更改,向接口判断是否有缓存。如果命中协商缓存则返回304状态码,并且从本地返回缓存内容。如果没有命中,则重新发起请求。

# 缓存的优先级

上面我们说过强制缓存的优先级高于协商缓存,Pragma的优先级高于Cache-Control,那么其他缓存的优先级顺序怎么样呢?:

Pragma > Cache-Control > Expires > ETag > Last-Modified

https://segmentfault.com/a/1190000020086923

https://juejin.im/post/5b70edd4f265da27df0938bc

# 3. 0.1+0.2 ?= 0.3

console.log(0.1+0.1) //0.2 console.log(0.1+0.2) //0.30000000000000004(精度最高保留到17位) 查阅资料之后,发现是因为像 0.1+0.2这样的操作对于计算机来说转换为二进制之后将是两个无限循环的数。而对于计算机而言是不允许有无限的,进行四舍五入之后双精度浮点数保留53位,结果为0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100转为十进制就是0.30000000000000004

十进制小数转换成二进制小数采用"乘2取整,顺序排列"法。具体做法如下:

用2乘十进制小数,可以得出积,将积的整数部分取出,再用2乘余下的小数部分,又得到一个积,再将积的整数部分取出,如此进行,直到积中的小数部分为零,或者达到所要求的精度为止。然后把取出的整数部分按顺序排列起来。

Number(3.1).toString(2) //"11.0001100110011001100110011001100110011001100110011001101"无限循环
1

0.1转为二进制:

Number(0.1).toString(2) //"0.0001100110011001100110011001100110011001100110011001101"无限循环
1

0.2转为二进制:

Number(0.2).toString(2) //"0.001100110011001100110011001100110011001100110011001101"无限循环
1

那为什么 x=0.1 能得到 0.1? 这是因为这个 0.1 并不是真正的0.1。这不是废话吗?别急,听我解释 JS精度范围。它最大可以表示2^53(9007199254740992), 长度是 16,所以可以使用 toPrecision(16) 来做精度运算,超过的精度会自动做凑整处理。 0.1.toPrecision(16) = "0.1000000000000000" 0.1.toPrecision(17) = "0.10000000000000001"

# 4. 计算结果

async function async1() {
  console.log('async1 start');//2
  await async2();
  console.log('async1 end');//6
}

async function async2() {
  console.log('async2');//3
}

console.log('script start');//1

setTimeout(function() {
  console.log('setTimeout');//8
}, 0);

async1();

new Promise(function(resolve) {
  console.log('promise1');//4
  resolve();
}).then(function() {
  console.log('promise2');//7
});

console.log('script end');//5
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

async/await原理?

async function test(){
  let a=await Promise.resolve('a');
  return a;
}
test().then(v=>console.log(v));

function test(){
  let _test=asyncToGenerator(function* (){
    let a=yield Promise.resolve('a');
    return a;
  })
  return _test.apply(this,arguments);
}

function asyncToGenerator(fn){
  return function(){
    return new Promise((resolve,reject)=>{
      let gen=fn.apply(this,arguments);
      function step(key,args){
        let res;
        try{
          res=gen[key](args);
        }catch(e){
          reject(e);
        }
        let {done,value}=res;
        if(done){
          resolve(value);
        }else{
          Promise.resolve(value).then(v=>step('next',v),e=>step('throw',e));
        }
      }
      step('next');
    })
  }
}
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
29
30
31
32
33
34
35
36

# 5. Promise.all代码实现

Promise.all = function(arr) {
  return new Promise((resolve,reject)=>{
    let res = [];
    let count = 0;
    for(let i=0;i<arr.length;i++){
      this.resolve(arr[i]).then((val)=>{
        res.push(arr[i]);
        count++;
        if(count===arr.length)resolve(res);
      },err=>{
        reject(err);
      })
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 6. 遍历二叉树把每一层级打印出来到二维数组。

function TreeNode(val) {
    this.val = val;
    this.left = null;
    this.right = null;
}

    1
   / \
  2   3
     / \
    4   5

function lvl(root) {
  let res=[];
  let dep=(node,level)=>{
    if(!node){
      return
    }
    res[level]?res[level].push(node.val):res[level]=[node.val];
    ++level;
    dep(node.left,level);
    dep(node.right,level);
  }
  dep(root,0)
  return res;
}

[
    [1],
    [2, 3],
    [4, 5],
];
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
29
30
31
32

# 过程

简单的自我介绍,感觉面试官没在意。先出了一道CDN的理解题,然后通过这个根据你回答还有什么缓存,我回答http缓存。然后理解http缓存静态资源有几种方式,怎么实现强缓存、实现协商缓存,怎么实现没答上来。然后出了0.1+0.2不等于0.3为啥。然后出了异步执行的题,由于前面http缓存的影响有点紧张,忘完了,promise和async/await理解也不够没答上来,追问async/await语法糖如何实现promise。Promise.all没写出来,因为对返回Promise和.all调用this理解不够没写出来。最后出了一道二叉树题,没答上来,结束。建议我对es6种async、Promise多总结。

# 总结

自我理解不够深,只靠以前博客看的,没有深挖,临场发挥就忘了。要加强对http、算法、promise/async/await理解和学习。