前端面试常见题汇总(持续更新中...)

AmsChan ... 2022-05-09 17:01 面试
  • 面试
  • 前端
大约 12 分钟

# 汇总

# 网络

  1. UDP特点:无连接、传输速度快、不保证传输可靠性,适用于音视频场景。

  2. 怎么保证UDP的接收顺序:在包头加上序号。

  3. TCP为什么要四次挥手:确保双方都已经准备好断开连接,比如服务端发出断开请求,然后客户端返回了ACK,但是这个ACK仅仅表示客户端收到了断开请求,而不是同意了断开,因为客户端可能还有数据在发送,所以还要客户端准备好了再向服务端发送断开请求,这时候已经3次挥手了,还需要服务端返回确认帧来告诉客户端它收到了断开请求。然后才能保证双方都做好了断开连接的准备。

  4. HTTP状态码:

    • 分类:

      • 1xx:表示消息;
      • 2xx:表示成功;
      • 3xx:表示重定向;
      • 4xx:表示请求错误;
      • 5xx:表示服务器错误。
    • 1xx:

      表示请求被接收,需要继续处理,临时响应。

      • 100:这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端应当继续发送请求的剩余部分,或者如果请求已经完成,忽略这个响应。服务器必须在请求完成后向客户端发送一个最终响应。
      • 101:服务器根据客户端的请求切换协议,主要用于websocket或http2升级。
    • 2xx:

      表示请求已成功被服务器接收、理解并接受。

      • 200(成功):请求已成功,请求所希望的响应头或数据体将随之返回。
      • 201(已创建):请求成功并且服务器创建了新的资源。
      • 202(已创建):服务器已经接收请求,但尚未处理。
      • 203(非授权消息):服务器已成功处理请求,但返回的信息可能来自另一来源。
      • 204(无内容):服务器成功处理请求,但没有返回任何内容。
      • 205(重置内容):服务器成功处理请求,但没有返回任何内容。
      • 206(部分内容):服务器成功处理了部分请求。

# 安全

  1. xss(跨站脚本攻击):允许攻击者将恶意代码植入到提供给其它用户使用的页面中。这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。

    分类:

    • 存储型:

      (1) 攻击者将恶意代码提交到目标网站的数据库中;

      (2) 用户打开目标网站时,网站服务端将恶意代码从数据库取出,返回至浏览器;

      (3) 浏览器接收到响应后解析执行,混在其中的恶意代码也被执行;

      (4) 恶意代码窃取用户数据并发送到攻击者的服务器,或冒充用户行为,调用目标网站接口执行攻击者指定的操作。

    • 反射型:常见于通过 URL 传递参数的功能,如网站搜索、跳转等。

      (1) 攻击者构造出特殊的URL,包含恶意代码;

      (2) 用户打开带有恶意代码的URL时,网站服务端将恶意代码从URL取出,拼接在HTML中返回给浏览器;

      (3) 用户浏览器接收到响应后解析执行,其中的恶意代码也被执行;

      (4) 恶意代码窃取用户数据并发送到攻击者的服务器,或冒充用户行为,调用目标网站接口执行攻击者指定的操作。

    ==反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。==

    • DOM型:

      (1) 攻击者构造出特殊的URL,包含恶意代码;

      (2) 用户浏览器接收到响应后解析执行,其中的恶意代码也被执行;

      (3) 恶意代码窃取用户数据并发送到攻击者的服务器,或冒充用户行为,调用目标网站接口执行攻击者指定的操作。

    预防:

    • 攻击者提交恶意代码:
      • 用户输入过程中,前端过滤用户输入的恶意代码。但是如果攻击者绕开前端请求,直接构造请求就不能预防了。
      • 在后端写入数据库前,对输入进行过滤,然后把内容给前端,但是这个内容在不同地方就会有不同显示。
    • 浏览器执行恶意代码:
      • 在使用 .innerHTML.outerHTMLdocument.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent.setAttribute() 等。
      • 如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTMLouterHTML 的 XSS 隐患。
      • DOM 中的内联事件监听器,如 locationonclickonerroronloadonmouseover 等,<a> 标签的 href 属性,JavaScript 的 eval()setTimeout()setInterval() 等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。
  2. csrf(跨站请求伪造):攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。

    典型的CSRF攻击:

    ​ (1) 受害者登录a.com,并保留了登录凭证(Cookie);

    ​ (2) 攻击者引诱受害者访问了b.com;

    ​ (3) b.com 向 a.com 发送了一个请求:a.com/act=xx。浏览器会默认携带a.com的Cookie;

    ​ (4) a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发 送的请求;

    ​ (5) a.com以受害者的名义执行了act=xx;

    ​ (6) 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操 作。

    特点:

    • 攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生。
    • 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据。
    • 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”。
    • 跨站请求可以用各种方式:图片URL、超链接、CORS、Form提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪。

    预防:

    • 阻止不明外域的访问
      • 同源检测
      • Samesite Cookie
    • 提交时要求附加本域才能获取的信息
      • CSRF Token
      • 双重Cookie验证
  3. sql注入:将恶意的 Sql查询或添加语句插入到应用的输入参数中,再在后台 Sql服务器上解析执行进行的攻击。

    注入流程:

    • 找出SQL漏洞的注入点;
    • 判断数据库的类型以及版本;
    • 猜解用户名和密码;
    • 利用工具查找Web后台管理入口;
    • 入侵和破坏。

    预防:

    • 严格检查输入变量的类型和格式;
    • 过滤和转义特殊字符;
    • 对访问数据库的Web应用程序采用Web应用防火墙。

# 工程化

  1. webpack:

    • 打包过程:识别入口文件、针对import关键字识别模块依赖、分析代码、转换代码、编译代码、输出代码。
    • loader:文件加载器,比如css-loader,ts-loader等可以对文件进行相应的转译(如编译、压缩)。
    • plugin:作为扩展使用,增强webpack的能力,webpack运行的生命周期会广播出许多时间,plugin通过监听这些事件,在适当的时机通过webpack提供的api改变输出结果。

# 浏览器

  1. 重绘:由于节点几何属性样式发生改变而不会影响布局。如outlinevisibilitycolorbackground-color

  2. 回流:布局发生改变。(影响浏览器性能的因素,因为其变化涉及到部分页面或整个页面的布局更新)。

  3. 何时发生回流:

    • 添加或删除可见DOM元素;
    • 元素位置发生改变;
    • 元素尺寸发生改变;
    • 内容发生改变;
    • 页面开始渲染;
    • 浏览器窗口尺寸发生改变。

    备注:回流一定发生重绘,重绘不一定发生回流。

  4. 浏览器渲染过程:

    (1) 解析HTML,生成DOM树;同时解析样式表,生成CSS树;

    (2) 合并DOM树和CSSOM树,生成渲染树;

    (3) Layout(回流):根据生成的渲染树,进行回流,得到节点的几何信息(位置、大小);

    (4) Painting(重绘):根据渲染树及回流得到的几何信息,得到节点的绝对像素;

    (5) Display:将像素发送给GPU,展示在页面上(GPU将合成层合并为一个层,并展示在页面中)。

  5. 浏览器构建渲染树流程:

    (1) 遍历DOM树的某个可见节点;

    (2) 对于每个可见节点,从CSSOM树中找到对应的规则并应用;

    (3) 根据每个可见节点及其对应样式,组合生成渲染树。

    备注(不可见节点):

    • script、link、meta等
    • 通过css隐藏的节点。如display: none,visibility和opactity隐藏的节点仍会在渲染树中。

# HTML/CSS

# CSS3加速原理:

# BFC(Block Formatting Context):

# 触发BFC的CSS:
  • overflow: hidden
  • float: left/right
  • display: inline-block
  • position: absolute/fixed
  • display: table-cell/flex/grid

# BFC规则:

  • BFC就是一个块级元素,块级元素会在垂直方向一个接一个的排列;

  • BFC就是页面中的一个隔离的独立容器,容器里的标签不会影响到外部标签;

  • 垂直方向的距离由margin决定, 属于同一个BFC的两个相邻的标签外边距会发生重叠;

  • 计算BFC的高度时,浮动元素也参与计算。

# BFC解决问题:

  • 使用float脱离文档流,高度塌陷;
  • margin边距重叠;
  • 两栏布局问题。

# JS

  1. ES6模块和CommonJs的区别:

# React

  1. virtual dom:react中render方法得到的并不是真实的dom节点,而是保存于内存中的js对象。
    • 抽象了渲染过程,使得可以更好地实现跨平台。
    • 初次渲染比较慢,因为中间需要计算虚拟节点树。
    • 对于节点比较少的页面,频繁的更新节点使用虚拟dom效率反而不高。
  2. diff算法:
    • 提升界面渲染的速度和性能,计算出真正改变的节点,不需要重新渲染整个页面
    • 三大算法:
      • tree diff:节点树之间的比较,只比较同层级的节点。对于跨层级的节点移动,在react中只会有创建和、删除的操作。比如A节点有B、C两个叶子节点,新的节点树变成了A节点的子节点只有B,而B节点在子节点是C。此时react有两个操作:删除A节点下的C节点在B节点下创建C节点。(一般不建议这么做)
      • component diff:如果是同一类组件,那么继续按照同层级的比较(用户可以通过shouldComponentUpdate()的方法来决定是否继续进行比较),如果不是同类型的组件,那么就替换整个组件下的所有子节点。
      • element diff:react对于同层级的节点的更新提供了创建、删除、移动三种操作,通过节点的key和diff来判断新老节点。
  3. ref:操作原生js的一个桥梁/通道。
  4. ErrorBoundary:作用类似于try catch,主要是为了防止js执行错误时页面白屏,当js报错时可以跳转到预留的页面,当然,怎么处理报错可以工具自己需求调整。

# 手写函数

# Debounce

const debounce = (func, ms) => {
  let timer = null;
  return function (...rest) {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      func.apply(this, rest);
    }, ms);
  };
};
1
2
3
4
5
6
7
8
9
10
11

# Throttle

// 方式1
const throttle = (func, ms) => {
  let timestamp = Date.now();
  return function (...rest) {
    if (Date.now() - timestamp < ms) {
      return;
    }
    func.apply(this, rest);
    timestamp = Date.now();
  };
};

// 方式2
const throttle = (func, ms) => {
  let flag = true;
  return function (...args) {
    if (!flag) return;
    flag = false;
    setTimeout(() => {
      func.apply(this, args);
      flag = true;
    }, ms);
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 函数柯里化

const curry = func => {
  return function curried(...args) {
    // 当实参数量大于/等于func定义的形参
    if (args.length >= func.length) {
      return func.apply(this, args);
    }
    return function (...args2) {
      return curried.apply(this, args.concat(args2));
    };
  };
};
1
2
3
4
5
6
7
8
9
10
11

# 深拷贝

const deepClone = target => {
  if (typeof target !== 'object') {
    return target;
  } else if (Array.isArray(target)) {
    const arr = [];
    target.forEach(el => {
      if (typeof el !== 'object') {
        arr.push(el);
      } else {
        arr.push(deepClone(el));
      }
    });
    return arr;
  } else if (target instanceof RegExp) {
    return new RegExp(target.source, target.flags);
  } else if (target instanceof Date) {
    return new Date(target);
  } else if (target instanceof Function) {
    return function () {
      return target.apply(this, arguments);
    };
  } else {
    const obj = {};
    Object.keys(target).forEach(key => {
      if (typeof target[key] !== 'object') {
        obj[key] = target[key];
      } else {
        obj[key] = deepClone(target[key]);
      }
    });
    return obj;
  }
};
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

# 数组扁平化

const arrayFlatten = arr => {
  const newArr = [];
  arr.forEach(el => {
    if (Array.isArray(el)) {
      newArr.push(...arrayFlatten(el));
    } else {
      newArr.push(el);
    }
  });
  return newArr;
};

// [1, [2, [3]]] => [1, 2, 3]
1
2
3
4
5
6
7
8
9
10
11
12
13

# 对象扁平化

const objectFlatten = (obj, key = '', newObj = {}) => {
  Object.keys(obj).forEach(k => {
    if (typeof obj[k] !== 'object') {
      newObj[`${key}${k}`] = obj[k];
    } else if (Array.isArray(obj[k])) {
      obj[k].forEach((el, i) => {
        if (typeof el !== 'object') {
          newObj[`${key}${k}[${i}]`] = obj[k][i];
        } else {
          objectFlatten(el, `${key}${k}.`, newObj);
        }
      });
    } else {
      objectFlatten(obj[k], `${key}${k}.`, newObj);
    }
  });
  return newObj;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 对象反扁平化

const reverseObjectFlatten = (obj, newObj = {}) => {
  Object.keys(obj).forEach(el => {
    const keys = el.split('.');
    let temp = newObj;
    const cur = temp;
    keys.forEach((key, i) => {
      const match = key.match(/.+(?=(\[[0-9]+\])$)/);
      if (match) {
        if (!temp[match[0]]) {
          temp[match[0]] = [];
        }
        const index = parseInt(match[1][1]);
        if (i === keys.length - 1) {
          temp[match[0]][index] = obj[el];
        } else {
          temp[match[0]][index] = {};
        }
        temp = temp[match[0]][index];
      } else {
        if (i === keys.length - 1) {
          temp[key] = obj[el];
        } else {
          if (!temp[key]) {
            temp[key] = {};
          }
          temp = temp[key];
        }
      }
    });
    Object.assign(newObj, cur);
  });
  return newObj;
};

// { a: 1, 'b.c': 3, 'b.arr[0].d': 1, 'b.arr[1]': 2 } 
// => { a: 1, b: { c: 3, arr: [ {d: 1}, 2 ] } }
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

# 发布订阅

class EventEmitter {
  constructor() {
    this.callback = {};
    this.curId = 0;
  }

  subscribe(name, cb, once = false) {
    if (!this.callback[name]) {
      this.callback[name] = [];
    }
    this.callback[name].push({ id: this.curId++, once, cb });
  }

  once(name, cb) {
    this.subscribe(name, cb, true);
  }

  notify(name) {
    if (this.callback[name]) {
      this.callback[name].forEach(item => {
        item.cb();
        if (item.once) {
          this.cancel(name, item.id);
        }
      });
    }
  }

  cancel(name, id) {
    if (this.callback[name]) {
      this.callback[name] = this.callback[name].filter(item => item.id !== id);
    }
  }
}
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

# 图片懒加载

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图片懒加载</title>
    <style>
      .image-container {
        overflow: scroll;
        width: 90vh;
        height: 100vh;
        margin-left: 50%;
        transform: translateX(-50%);
        background-color: azure;
      }

      .img {
        width: 100%;
        height: 25%;
      }
    </style>
  </head>

  <body>
    <div class="image-container">
      <img class="img" data-src="./images/1.jpg">
      <img class="img" data-src="./images/2.jpg">
      <img class="img" data-src="./images/3.jpg">
      <img class="img" data-src="./images/4.jpg">
      <img class="img" data-src="./images/5.jpg">
    </div>
  </body>

  <script>
    const lasyLoad = (entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          entry.target.src = entry.target.dataset.src;
        } else {
          entry.target.src = '';
        }
      });
    }

    const observer = new IntersectionObserver(lasyLoad, {
      threshold: 0,  // 阈值,父子元素交集大小超过该值就会执行回调函数,默认为0
    });

    const imgs = document.getElementsByClassName('img')
    for (let el of imgs) {
      observer.observe(el)
    }
  </script>

</html>
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57