进大厂必备的前端面试题

108 阅读5分钟

简单讲解一下 http2 的多路复用

  • HTTP2 采用二进制格式传输,取代了 HTTP1.x 的文本格式传输,二进制格式解析更高效
  • 多路复用代替了 HTTP1.x 的序列和阻塞机制,所有的相同域名请求都可以通过同一个 TCP 连接并发完成。
  • 在 HTTP1.x 中,并发多个请求需要多个 TCP 连接,虽然有持久链接,但浏览器为了控制资源仍会有 6-8个 TCP 连接限制。
  • HTTP2 中同域名下所有通信都在单个连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗,没有了HTTP1.x的并发 TCP 连接限制。
  • 单个连接上可以并行交错的请求和响应,之间互不干扰。

谈谈你对 TCP 三次握手和四次挥手的理解

image.png

  • 简单版

握手阶段:嗨,我是a -> 嗨,我是b,这是我的号码 -> 好的,我知道你的号码了,我们建立连接了。 挥手阶段:a对b通话,a提出再见 -> b说知道了,那就再见吧 -> a听到确认后,就挂断电话了 -> 断开连接。

A、B 机器正常连接后,B 机器突然重启,问 A 此时处于 TCP 什么状态?如何消除服务器程序中的这个状态?

  • b 会在重启之后进入 tcp 状态机的 listen 状态,只要当 a 重新发送一个数据包(无论是 syn 包或者是应用数据),b 端应该会主动发送一个带 rst 位的重置包来进行连接重置,所以 a 应该在 syn_sent 状态。

写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

key 是给每一个 vnode 设置唯一id,可以依靠key值,更准确、更快的,找到最小更新范围,进行更新节点,提升性能。

['1', '2', '3'].map(parseInt)输出什么?为什么?

第一眼看到这个题目的时候,脑海跳出的答案是[1, 2, 3],但真正的答案是[1,NaN, NaN]

首先让我们回顾一下,map 函数的第一个参数 callback。这个 callback 一共可以接收三个参数,其中第一个参数代表当前被处理的元素,而第二个参数代表该元素的索引。

arr.map(callback: (value: T, index: number, array: T[]) => U, thisArg?:any);

parseInt 则是用来解析字符串的,使字符串成为指定基数的整数。接收两个参数,第一个表示被处理的值(字符串),第二个表示为解析时的基数。

parseInt(string, radix);

了解这两个函数后,我们可以模拟一下运行情况

parseInt('1', 0); // radix 为 0 时,且 string 参数不以“0x”和“0”开头时按照 10 为基数处理。这个时候返回 1
parseInt('2', 1); //基数为 1(1 进制)表示的数中,最大值小于 2,所以无法解析,返回 NaN
parseInt('3', 2); //基数为 2(2 进制)表示的数中,最大值小于 3,所以无法解析,返回 NaN

什么是防抖和节流?有什么区别?如何实现?

  • 防抖 —— 触发高频事件后 n 秒后函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间;
function debounce(fn, delay = 500) {
  let timeout;
  // 创建一个标记用来存放定时器的返回值
  return function () {
    if (timeout) {
      clearTimeout(timeout);
      timeout = undefined;
    }
    // 每当用户输入的时候把前一个 setTimeout clear 掉
    timeout = setTimeout(() => {
      // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的nterval 间隔内如果还有字符输入的话,就不会执行 fn 函数
      fn.apply(this, arguments);
    }, delay);
  };
};

比如说:你玩射击游戏,鼠标按的贼快但只能打出1发子弹。

  • 节流 —— 高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。
function throttle(fn, delay = 200) {
  let canRun = true; // 通过闭包保存一个标记 
  return function () {
     // 在函数开头判断标记是否为 true,不为 true 则 return 
    if (!canRun) return;
    canRun = false; // 立即设置为 false 
    // 将外部传入的函数的执行放在 setTimeout 中
    let timer = setTimeout(() => { 
      fn.apply(this, arguments);
      // 最后在 setTimeout 执行完毕后再把标记设置为 true(关键) 表 示可以执行下一次循环了。
      // 当定时器没有执行的时候标记永远是 false,在开头 被 return 掉。
      canRun = true;
      clearTimeout(timer);
    }, delay);
  }
}

比如说:你玩射击游戏,鼠标快速按了10下,但是只打出3发子弹。

介绍下 Set、Map、WeakSet 和 WeakMap 的区别?

Set

  • 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用

WeakSet

  • 成员都是对象;
  • 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存 DOM 节点,不容易造成内存泄漏;

Map

  • 本质上是键值对的集合,类似集合;可以遍历,方法很多,可以跟各种数据格式转换。

WeakMap

  • 只接受对象为键名(null 除外),不接受其他类型的值作为键名;
  • 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的;
  • 不能遍历,方法有 get、set、has、delete。

介绍下深度优先遍历和广度优先遍历,如何实现?

  • 深度优先遍历是指从某个顶点出发,首先访问这个顶点,然后找出刚访问这个结点的第一个未被访问的邻结点,然后再以此邻结点为顶点,继续找它的下一个顶点进行访问。
  • 重复此步骤,直至所有结点都被访问完为止。
// 深度优先遍历的递归写法 
function deepTraversal(node) {
  let nodes = [];
  if( node ) { 
    nodes.push(node);
    let childrens = node.children;
    for (let i = 0; i < childrens.length; i++){
      deepTraversal(childrens[i]);
    }
  }
  return nodes;
}
  • 广度优先遍历是从某个顶点出发,首先访问这个顶点,然后找出刚访问这个结点所有未被访问的邻结点,访问完后再访问这些结点中第一个邻结点的所有结点。
  • 重复此方法,直到所有结点都被访问完为止。
function wideTraversal(node) {
  let nodes = [], i = 0;
  if (node) {
    nodes.push(node);
    wideTraversal(node.nextElementSibling);
    node = nodes[i++];
    wideTraversal(node.firstElementChild);
  }
  return nodes;
}