2023 前端 网络和工程化面试题汇总

282 阅读1小时+

网络面试题汇总

  1. 简述 TCP 连接的过程(淘系)

    参考答案:

    TCP 协议通过三次握手建立可靠的点对点连接,具体过程是:

    首先服务器进入监听状态,然后即可处理连接

    第一次握手:建立连接时,客户端发送 syn 包到服务器,并进入 SYN_SENT 状态,等待服务器确认。在发送的包中还会包含一个初始序列号 seq。此次握手的含义是客户端希望与服务器建立连接。

    第二次握手:服务器收到 syn 包,然后回应给客户端一个 SYN+ACK 包,此时服务器进入 SYN_RCVD 状态。此次握手的含义是服务端回应客户端,表示已收到并同意客户端的连接请求。

    第三次握手:客户端收到服务器的 SYN 包后,向服务器再次发送 ACK 包,并进入 ESTAB_LISHED 状态。

    最后,服务端收到客户端的 ACK 包,于是也进入 ESTAB_LISHED 状态,至此,连接建立完成

  2. 介绍下 HTTPS 中间人攻击

    参考答案:

    针对 HTTPS 攻击主要有 SSL 劫持攻击和 SSL 剥离攻击两种。

    SSL 劫持攻击是指攻击者劫持了客户端和服务器之间的连接,将服务器的合法证书替换为伪造的证书,从而获取客户端和服务器之间传递的信息。这种方式一般容易被用户发现,浏览器会明确的提示证书错误,但某些用户安全意识不强,可能会点击继续浏览,从而达到攻击目的。

    SSL 剥离攻击是指攻击者劫持了客户端和服务器之间的连接,攻击者保持自己和服务器之间的 HTTPS 连接,但发送给客户端普通的 HTTP 连接,由于 HTTP 连接是明文传输的,即可获取客户端传输的所有明文数据。

  3. 介绍下 http1.0http1.1http2.0 协议的区别?

    参考答案:

    首先说 http1.0

    它的特点是每次请求和响应完毕后都会销毁 TCP 连接,同时规定前一个响应完成后才能发送下一个请求。这样做有两个问题:

    1. 无法复用连接

      每次请求都要创建新的 TCP 连接,完成三次握手和四次挥手,网络利用率低

    2. 队头阻塞

      如果前一个请求被某种原因阻塞了,会导致后续请求无法发送。

    然后是 http1.1

    http1.1 是 http1.0 的改进版,它做出了以下改进:

    1. 长连接

      http1.1 允许在请求时增加请求头connection:keep-alive,这样便允许后续的客户端请求在一段时间内复用之前的 TCP 连接

    2. 管道化

      基于长连接的基础,管道化可以不等第一个请求响应继续发送后面的请求,但响应的顺序还是按照请求的顺序返回。

    3. 缓存处理

      新增响应头 cache-control,用于实现客户端缓存。

    4. 断点传输

      在上传/下载资源时,如果资源过大,将其分割为多个部分,分别上传/下载,如果遇到网络故障,可以从已经上传/下载好的地方继续请求,不用从头开始,提高效率

    最后是 http2.0

    http2.0 进一步优化了传输效率,它主要有以下改进:

    1. 二进制分帧

      将传输的消息分为更小的二进制帧,每帧有自己的标识序号,即便被随意打乱也能在另一端正确组装

    2. 多路复用

      基于二进制分帧,在同一域名下所有访问都是从同一个 tcp 连接中走,并且不再有队头阻塞问题,也无须遵守响应顺序

    3. 头部压缩

      http2.0 通过字典的形式,将头部中的常见信息替换为更少的字符,极大的减少了头部的数据量,从而实现更小的传输量

    4. 服务器推

      http2.0 允许服务器直接推送消息给客户端,无须客户端明确的请求

  4. 为什么 HTTP1.1 不能实现多路复用(腾讯)

    参考答案:

    HTTP/1.1 不是二进制传输,而是通过文本进行传输。由于没有流的概念,在使用并行传输(多路复用)传递数据时,接收端在接收到响应后,并不能区分多个响应分别对应的请求,所以无法将多个响应的结果重新进行组装,也就实现不了多路复用。

  5. 简单讲解一下 http2 的多路复用(网易)

    参考答案:

    在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream)。 帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。 多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。

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

    TCP 协议通过三次握手建立可靠的点对点连接,具体过程是:

    首先服务器进入监听状态,然后即可处理连接

    第一次握手:建立连接时,客户端发送 syn 包到服务器,并进入 SYN_SENT 状态,等待服务器确认。在发送的包中还会包含一个初始序列号 seq。此次握手的含义是客户端希望与服务器建立连接。

    第二次握手:服务器收到 syn 包,然后回应给客户端一个 SYN+ACK 包,此时服务器进入 SYN_RCVD 状态。此次握手的含义是服务端回应客户端,表示已收到并同意客户端的连接请求。

    第三次握手:客户端收到服务器的 SYN 包后,向服务器再次发送 ACK 包,并进入 ESTAB_LISHED 状态。

    最后,服务端收到客户端的 ACK 包,于是也进入 ESTAB_LISHED 状态,至此,连接建立完成

    当需要关闭连接时,需要进行四次挥手才能关闭

    1. Client 向 Server 发送 FIN 包,表示 Client 主动要关闭连接,然后进入 FIN_WAIT_1 状态,等待 Server 返回 ACK 包。此后 Client 不能再向 Server 发送数据,但能读取数据。
    2. Server 收到 FIN 包后向 Client 发送 ACK 包,然后进入 CLOSE_WAIT 状态,此后 Server 不能再读取数据,但可以继续向 Client 发送数据。
    3. Client 收到 Server 返回的 ACK 包后进入 FIN_WAIT_2 状态,等待 Server 发送 FIN 包。
    4. Server 完成数据的发送后,将 FIN 包发送给 Client,然后进入 LAST_ACK 状态,等待 Client 返回 ACK 包,此后 Server 既不能读取数据,也不能发送数据。
    5. Client 收到 FIN 包后向 Server 发送 ACK 包,然后进入 TIME_WAIT 状态,接着等待足够长的时间(2MSL)以确保 Server 接收到 ACK 包,最后回到 CLOSED 状态,释放网络资源。
    6. Server 收到 Client 返回的 ACK 包后便回到 CLOSED 状态,释放网络资源。
  7. 介绍 HTTPS 握手过程

    参考答案:

    1. 客户端请求服务器,并告诉服务器自身支持的加密算法以及密钥长度等信息
    2. 服务器响应公钥和服务器证书
    3. 客户端验证证书是否合法,然后生成一个会话密钥,并用服务器的公钥加密密钥,把加密的结果通过请求发送给服务器
    4. 服务器使用私钥解密被加密的会话密钥并保存起来,然后使用会话密钥加密消息响应给客户端,表示自己已经准备就绪
    5. 客户端使用会话密钥解密消息,知道了服务器已经准备就绪。
    6. 后续客户端和服务器使用会话密钥加密信息传递消息
  8. HTTPS 握手过程中,客户端如何验证证书的合法性

    参考答案:

    1. 校验证书的颁发机构是否受客户端信任。
    2. 通过 CRL 或 OCSP 的方式校验证书是否被吊销。
    3. 对比系统时间,校验证书是否在有效期内。
    4. 通过校验对方是否存在证书的私钥,判断证书的网站域名是否与证书颁发的域名一致。
  9. Http 状态码 301 和 302 的应用场景分别是什么

    参考答案:

    301 表示永久重定向,302 表示临时重定向。

    如果浏览器收到的是 301,则会缓存重定向的地址,之后不会再重新请求服务器,直接使用缓存的地址请求,这样可以减少请求次数。

    但如果浏览器收到的是 302,则不会缓存重定向地址,浏览器将来会继续以原有地址请求。

    因此,301 适合地址永久转移的场景,比如域名变更;而 302 适合临时转移的场景,比如首页临时跳转到活动页

  10. cookie 和 token 都存放在 header 中,为什么不会劫持 token?

    参考答案:

    由于浏览器会自动发送 cookie 到服务器,因此攻击者可以利用这种特点进行 csrf 攻击。

    而通常 token 是不放到 cookie 中的,需要浏览器端使用 JS 自行保存到 localstorage 中,在请求时也需要手动的加入到请求头中,因此不容易引发 csrf 攻击。

  11. 介绍下如何实现 token 加密

    参考答案:

    以最常见的 token 格式 jwt 为例

    token 分为三段,分别是 header、payload、signature

    其中,header 标识签名算法和令牌类型;payload 标识主体信息,包含令牌过期时间、发布时间、发行者、主体内容等;signature 是使用特定的算法对前面两部分进行加密,得到的加密结果。

    token 有防篡改的特点,如果攻击者改动了前面两个部分,就会导致和第三部分对应不上,使得 token 失效。而攻击者不知道加密秘钥,因此又无法修改第三部分的值。

    所以,在秘钥不被泄露的前提下,一个验证通过的 token 是值得被信任的。

  12. 说下单点登录(新东方)

    参考答案:

    SSO 一般都需要一个独立的认证中心(passport),子系统的登录均得通过 passport,子系统本身将不参与登录操作,当一个系统成功登录以后,passport 将会颁发一个令牌给各个子系统,子系统可以拿着令牌会获取各自的受保护资源,为了减少频繁认证,各个子系统在被 passport 授权以后,会建立一个局部会话,在一定时间内可以无需再次向 passport 发起认证。

    具体流程是:

    1. 用户访问系统 1 的受保护资源,系统 1 发现用户未登录,跳转至 sso 认证中心,并将自己的地址作为参数
    2. sso 认证中心发现用户未登录,将用户引导至登录页面
    3. 用户输入用户名密码提交登录申请
    4. sso 认证中心校验用户信息,创建用户与 sso 认证中心之间的会话,称为全局会话,同时创建授权令牌
    5. sso 认证中心带着令牌跳转会最初的请求地址(系统 1)
    6. 系统 1 拿到令牌,去 sso 认证中心校验令牌是否有效
    7. sso 认证中心校验令牌,返回有效,注册系统 1
    8. 系统 1 使用该令牌创建与用户的会话,称为局部会话,返回受保护资源
    9. 用户访问系统 2 的受保护资源
    10. 系统 2 发现用户未登录,跳转至 sso 认证中心,并将自己的地址作为参数
    11. sso 认证中心发现用户已登录,跳转回系统 2 的地址,并附上令牌
    12. 系统 2 拿到令牌,去 sso 认证中心校验令牌是否有效
    13. sso 认证中心校验令牌,返回有效,注册系统 2
    14. 系统 2 使用该令牌创建与用户的局部会话,返回受保护资源
  13. http1.1 是如何复用 tcp 连接的?(网易)

    参考答案:

    客户端请求服务器时,通过请求行告诉服务器使用的协议是 http1.1,同时在请求头中附带connection:keep-alive(为保持兼容),告诉服务器这是一个长连接,后续请求可以重复使用这一次的 TCP 连接。

    这样做的好处是减少了三次握手和四次挥手的次数,一定程度上提升了网络利用率。但由于 http1.1 不支持多路复用,响应顺序必须按照请求顺序抵达客户端,不能真正实现并行传输,因此在 http2.0 出现之前,实际项目中往往把静态资源,比如图片,分发到不同域名下的资源服务器,以便实现真正的并行传输。

  14. 文件上传如何做断点续传(网易)

    参考答案:

    客户端将文件的二进制内容进行分片,每片数据按顺序进行序号标识,上传每片数据时同时附带其序号。服务器接收到每片数据时,将其保存成一个临时文件,并记录每个文件的 hash 和序号。

    若上传中止,将来再次上传时,可以向服务器索要已上传的分片序号,客户端仅需上传剩余分片即可。

    当全部分片上传完成后,服务器按照分片的顺序组装成完整的文件,并删除分片文件。

  15. 介绍 SSL 和 TLS(寺库)

    参考答案:

    它们都是用于保证传输安全的协议,介于传输层和应用层之间,TLS 是 SSL 的升级版。

    它们的基本流程一致:

    1. 客户端向服务器端索要公钥,并使用数字证书验证公钥。
    2. 客户端使用公钥加密会话密钥,服务端用私钥解密会话密钥,于是得到一个双方都认可的会话密钥
    3. 传输的数据使用会话密钥加密,然后再传输,接收消息方使用会话密钥解密得到原始数据
  16. 说说网络的五层模型(寺库)

    参考答案:

    从上到下分别为:应用层、传输层、网络层、数据链路层、物理层。在发送消息时,消息从上到下进行打包,每一层会在上一层基础上加包,而接受消息时,从下到上进行解包,最终得到原始信息。

    其中:

    应用层主要面向互联网中的应用场景,比如网页、邮件、文件中心等等,它的代表协议有 http、smtp、pop3、ftp、DNS 等等

    传输层主要面向传输过程,比如 TCP 协议是为了保证可靠的传输,而 UDP 协议则是一种无连接的广播,它们提供了不同的传输方式

    网络层主要解决如何定位目标的问题,比如 IP、ICMP、ARP 等等

    数据链路层的作用是将数据可靠的传输到目标,比如常见的以太网协议、P2P 协议

    物理层是要规范网络两端使用的物理设备,比如蓝牙、wifi、光纤、网线接头等等

  17. GET 和 POST 的区别(流利说)

    参考答案:

    从 http 协议的角度来说,GET 和 POST 它们都只是请求行中的第一个单词,除了语义不同,其实没有本质的区别。

    之所以在实际开发中会产生各种区别,主要是因为浏览器的默认行为造成的。

    受浏览器的影响,在实际开发中,GET 和 POST 有以下区别:

    1. 浏览器在发送 GET 请求时,不会附带请求体
    2. GET 请求的传递信息量有限,适合传递少量数据;POST 请求的传递信息量是没有限制的,适合传输大量数据。
    3. GET 请求只能传递 ASCII 数据,遇到非 ASCII 数据需要进行编码;POST 请求没有限制
    4. 大部分 GET 请求传递的数据都附带在 path 参数中,能够通过分享地址完整的重现页面,但同时也暴露了数据,若有敏感数据传递,不应该使用 GET 请求,至少不应该放到 path 中
    5. 刷新页面时,若当前的页面是通过 POST 请求得到的,则浏览器会提示用户是否重新提交。若是 GET 请求得到的页面则没有提示。
    6. GET 请求的地址可以被保存为浏览器书签,POST 不可以
  18. http 劫持是什么?

    参考答案:

    是指攻击者在客户端和服务器之间同时建立了连接通道,通过某种方式,让客户端请求发送到自己的服务器,然后自己就拥有了控制响应内容的能力,从而给客户端展示错误的信息。

  19. HTTP 劫持、DNS 劫持与 XSS

    参考答案:

    http 劫持是指攻击者在客户端和服务器之间同时建立了连接通道,通过某种方式,让客户端请求发送到自己的服务器,然后自己就拥有了控制响应内容的能力,从而给客户端展示错误的信息,比如在页面中加入一些广告内容。

    DNS 劫持是指攻击者劫持了 DNS 服务器,获得了修改 DNS 解析记录的权限,从而导致客户端请求的域名被解析到了错误的 IP 地址,攻击者通过这种方式窃取用户资料或破坏原有正常服务。

    XSS 是指跨站脚本攻击。攻击者利用站点的漏洞,在表单提交时,在表单内容中加入一些恶意脚本,当其他正常用户浏览页面,而页面中刚好出现攻击者的恶意脚本时,脚本被执行,从而使得页面遭到破坏,或者用户信息被窃取。

    要防范 XSS 攻击,需要在服务器端过滤脚本代码,将一些危险的元素和属性去掉或对元素进行HTML实体编码。

  20. 介绍 xss csrf 攻击

    参考答案:

    XSS:

    XSS 是指跨站脚本攻击。攻击者利用站点的漏洞,在表单提交时,在表单内容中加入一些恶意脚本,当其他正常用户浏览页面,而页面中刚好出现攻击者的恶意脚本时,脚本被执行,从而使得页面遭到破坏,或者用户信息被窃取。

    要防范 XSS 攻击,需要在服务器端过滤脚本代码,将一些危险的元素和属性去掉或对元素进行HTML实体编码。

    CSRF:

    CSRF 是跨站请求伪造,是一种挟制用户在当前已登录的Web应用上执行非本意的操作的攻击方法

    它首先引导用户访问一个危险网站,当用户访问网站后,网站会发送请求到被攻击的站点,这次请求会携带用户的cookie发送,因此就利用了用户的身份信息完成攻击

    防御 CSRF 攻击有多种手段:

    1. 不使用cookie
    2. 为表单添加校验的 token 校验
    3. cookie中使用sameSite字段
    4. 服务器检查 referer 字段
  21. https 验证身份也就是 TSL/SSL 身份验证的过程

    参考答案:

    1. 客户端请求服务器,并告诉服务器自身支持的加密算法以及密钥长度等信息
    2. 服务器响应公钥和服务器证书
    3. 客户端验证证书是否合法,然后生成一个会话密钥,并用服务器的公钥加密密钥,把加密的结果通过请求发送给服务器
    4. 服务器使用私钥解密被加密的会话密钥并保存起来,然后使用会话密钥加密消息响应给客户端,表示自己已经准备就绪
    5. 客户端使用会话密钥解密消息,知道了服务器已经准备就绪。
    6. 后续客户端和服务器使用会话密钥加密信息传递消息
  22. 为什么需要 CA 机构对证书签名

    参考答案:

    主要是为了解决证书的可信问题。如果没有权威机构对证书进行签名,客户端就无法知晓证书是否是伪造的,从而增加了中间人攻击的风险,https 就变得毫无意义。

  23. 身份验证过程中会涉及到密钥,对称加密,非对称加密,摘要的概念,请解释一下

    参考答案:

    密钥

    密钥是一种参数,它是在明文转换为密文或将密文转换为明文的算法中输入的参数。密钥分为对称密钥与非对称密钥,分别应用在对称加密和非对称加密上。

    对称加密

    对称加密又叫做私钥加密,即信息的发送方和接收方使用同一个密钥去加密和解密数据。对称加密的特点是算法公开、加密和解密速度快,适合于对大数据量进行加密,常见的对称加密算法有 DES、3DES、TDEA、Blowfish、RC5 和 IDEA。

    非对称加密

    非对称加密也叫做公钥加密。非对称加密与对称加密相比,其安全性更好。对称加密的通信双方使用相同的密钥,如果一方的密钥遭泄露,那么整个通信就会被破解。而非对称加密使用一对密钥,即公钥和私钥,且二者成对出现。私钥被自己保存,不能对外泄露。公钥指的是公共的密钥,任何人都可以获得该密钥。用公钥或私钥中的任何一个进行加密,用另一个进行解密。

    摘要

    摘要算法又称哈希/散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用 16 进制的字符串表示)。算法不可逆。

  24. webSocket 协议是什么,能简述一下吗?

    参考答案:

    websocket 协议 HTML5 带来的新协议,相对于 http,它是一个持久连接的协议,它利用 http 协议完成握手,然后通过 TCP 连接通道发送消息,使用 websocket 协议可以实现服务器主动推送消息。

    首先,客户端若要发起 websocket 连接,首先必须向服务器发送 http 请求以完成握手,请求行中的 path 需要使用ws:开头的地址,请求头中要分别加入upgrade、connection、Sec-WebSocket-Key、Sec-WebSocket-Version标记

    然后,服务器收到请求后,发现这是一个 websocket 协议的握手请求,于是响应行中包含Switching Protocols,同时响应头中包含upgrade、connection、Sec-WebSocket-Accept标记

    当客户端收到响应后即可完成握手,随后使用建立的 TCP 连接直接发送和接收消息。

  25. webSocket 与传统的 http 有什么优势

    参考答案:

    当页面中需要观察实时数据的变化(比如聊天、k 线图)时,过去我们往往使用两种方式完成

    第一种是短轮询,即客户端每隔一段时间就向服务器发送消息,询问有没有新的数据

    第二种是长轮询,发起一次请求询问服务器,服务器可以将该请求挂起,等到有新消息时再进行响应。响应后,客户端立即又发起一次请求,重复整个流程。

    无论是哪一种方式,都暴露了 http 协议的弱点,即响应必须在请求之后发生,服务器是被动的,无法主动推送消息。而让客户端不断的发起请求又白白的占用了资源。

    websocket 的出现就是为了解决这个问题,它利用 http 协议完成握手之后,就可以与服务器建立持久的连接,服务器可以在任何需要的时候,主动推送消息给客户端,这样占用的资源最少,同时实时性也最高。

  26. 如何劫持 https 的请求,提供思路

    参考答案:

    https 有防篡改的特点,只要浏览器证书验证过程是正确的,很难在用户不察觉的情况下进行攻击。但若能够更改浏览器的证书验证过程,便有机会实现 https 中间人攻击。

    所以,要劫持 https,首先要伪造一个证书,并且要想办法让用户信任这个证书,可以有多种方式,比如病毒、恶意软件、诱导等。一旦证书被信任后,就可以利用普通中间人攻击的方式,使用伪造的证书进行攻击。

  27. 怎样解决跨域问题?

    参考答案:

    1. 使用 JSONP

      这是一种古老的解决跨域问题的思路。

      在需要跨域请求时,事先准备好一个处理服务器数据的函数,然后生成一个<script>元素,src指向跨域站点,同时把准备好的函数名通过地址参数传递到服务器。

      跨域站点返回一段调用该函数的脚本,当客户端接收到脚本后就会运行事先准备的函数,从而实现跨域获取数据。

      JSONP 实现简单、兼容性好,但缺点也很明显,它只支持 get 请求,同时也有安全性问题,并且对服务器端代码侵入性比较强。

    2. 使用 cors

      在请求时,客户端使用一些特殊的请求头向服务器申请跨域访问,并通过这些请求头告诉服务器自己的行为。服务器根据自身的规则决定是否允许跨域,如果允许,则通过响应头告诉客户端可以发送跨域请求。

      cors 协议已被各种主流浏览器支持,它安全性高,同时也不会侵入服务器代码,是目前最主流的跨域方式

    除此之外,远古时期的跨域处理还包括 iframe、form 等,由于它们缺陷非常明显,故很少使用了。

  28. 前端如何实现即时通讯?

    参考答案:

    1. 短轮询。即客户端每隔一段时间就向服务器发送消息,询问有没有新的数据
    2. 长轮询,发起一次请求询问服务器,服务器可以将该请求挂起,等到有新消息时再进行响应。响应后,客户端立即又发起一次请求,重复整个流程。
    3. websocket,握手完毕后会建立持久性的连接通道,随后服务器可以在任何时候推送新消息给客户端
  29. HTTP 常用状态码 301 302 304 403

    参考答案:

    301 永久重定向,浏览器会把重定向后的地址缓存起来,将来用户再次访问原始地址时,直接引导用户访问新地址

    302 临时重定向,浏览器会引导用户进入新地址,但不会缓存原始地址,下一次用户访问源地址时,浏览器仍然要请求原地址的服务器

    304 资源未修改,服务器通过该状态码告诉客户端,请求的资源和过去一样,并没有任何变化,建议自行使用过去的缓存。通常,304 状态码的响应中,服务器不会附带任何的响应体。

    403 不允许访问。服务器通过该状态码告诉客户端,这个资源目前不允许访问。这种状态码通常出现在权限不足的情况下。

  30. 在浏览器地址栏输入地址,并按下回车键后,发生了哪些事情?

    1. 参考答案:

      1. 浏览器自动补全协议、端口
      2. 浏览器自动完成url编码
      3. 浏览器根据url地址查找本地缓存,根据缓存规则看是否命中缓存,若命中缓存则直接使用缓存,不再发出请求
      4. 通过DNS解析找到服务器的IP地址
      5. 浏览器向服务器发出建立TCP连接的申请,完成三次握手后,连接通道建立
      6. 若使用了HTTPS协议,则还会进行SSL握手,建立加密信道。使用SSL握手时,会确定是否使用HTTP2
      7. 浏览器决定要附带哪些cookie到请求头中
      8. 浏览器自动设置好请求头、协议版本、cookie,发出GET请求
      9. 服务器处理请求,进入后端处理流程。完成处理后,服务器响应一个HTTP报文给浏览器。
      10. 浏览器根据使用的协议版本,以及Connection字段的约定,决定是否要保留TCP连接。
      11. 浏览器根据响应状态码决定如何处理这一次响应
      12. 浏览器根据响应头中的Content-Type字段识别响应类型,如果是text/html,则对响应体的内容进行HTML解析,否则做其他处理
      13. 浏览器根据响应头的其他内容完成缓存、cookie的设置
      14. 浏览器开始从上到下解析HTML,若遇到外部资源链接,则进一步请求资源
      15. 解析过程中生成DOM树、CSSOM树,然后一边生成,一边把二者合并为渲染树(rendering tree),随后对渲染树中的每个节点计算位置和大小(reflow),最后把每个节点利用GPU绘制到屏幕(repaint)
      16. 在解析过程中还会触发一系列的事件,当DOM树完成后会触发DOMContentLoaded事件,当所有资源加载完毕后会触发load事件
  31. HTTPS 握手

    参考答案:

    1. 客户端请求服务器,并告诉服务器自身支持的加密算法以及密钥长度等信息
    2. 服务器响应公钥和服务器证书
    3. 客户端验证证书是否合法,然后生成一个会话密钥,并用服务器的公钥加密密钥,把加密的结果通过请求发送给服务器
    4. 服务器使用私钥解密被加密的会话密钥并保存起来,然后使用会话密钥加密消息响应给客户端,表示自己已经准备就绪
    5. 客户端使用会话密钥解密消息,知道了服务器已经准备就绪。
    6. 后续客户端和服务器使用会话密钥加密信息传递消息
  32. 网页验证码是干嘛的,是为了解决什么安全问题?

    参考答案:

    验证码主要用于让服务器区分请求是人还是机器发送的。这样做是为了避免某些程序恶意的提交大量信息到服务器,进而导致服务器产生大量的垃圾数据。有时,验证码也可以防止机器暴力破解用户密码,它通过在短时间内不断提交登录信息,尝试各种密码组合来达到破解的目的。

  33. http1.0、http2.0、http3.0 之间的区别

    参考答案:

    http1.0

    每次请求和响应完毕后都会销毁 TCP 连接,同时规定前一个响应完成后才能发送下一个请求。这样做有两个问题:

    1. 无法复用连接

      每次请求都要创建新的 TCP 连接,完成三次握手和四次挥手,网络利用率低

    2. 队头阻塞

      如果前一个请求被某种原因阻塞了,会导致后续请求无法发送。

    http2.0

    http2.0 优化了传输效率,它主要有以下改进:

    1. 二进制分帧

      将传输的消息分为更小的二进制帧,每帧有自己的标识序号,即便被随意打乱也能在另一端正确组装

    2. 多路复用

      基于二进制分帧,在同一域名下所有访问都是从同一个 tcp 连接中走,并且不再有队头阻塞问题,也无须遵守响应顺序

    3. 头部压缩

      http2.0 通过字典的形式,将头部中的常见信息替换为更少的字符,极大的减少了头部的数据量,从而实现更小的传输量

    4. 服务器推

      http2.0 允许服务器直接推送消息给客户端,无须客户端明确的请求

    http3.0

    http3.0 目前还在草案阶段,它完全抛弃了 TCP 协议,转而使用 UDP 协议,是为了进一步提升性能。

    虽然 http2.0 进行了大量的优化,但它无法摆脱 TCP 协议本身的问题,比如建立连接时间长、对头阻塞问题等等。

    为了保证传输的可靠性,http3.0 使用了 QUIC 协议。

  34. cookie/sessionStorage/localStorage 的区别

    参考答案:

    cookie、sessionStorage、localStorage 都是保存本地数据的方式

    其中,cookie 兼容性较好,所有浏览器均支持。浏览器针对 cookie 会有一些默认行为,比如当响应头中出现set-cookie字段时,浏览器会自动保存 cookie 的值;再比如,浏览器发送请求时,会附带匹配的 cookie 到请求头中。这些默认行为,使得 cookie 长期以来担任着维持登录状态的责任。与此同时,也正是因为浏览器的默认行为,给了恶意攻击者可乘之机,CSRF 攻击就是一个典型的利用 cookie 的攻击方式。虽然 cookie 不断的改进,但前端仍然需要另一种更加安全的保存数据的方式

    HTML5 新增了 sessionStorage 和 localStorage,前者用于保存会话级别的数据,后者用于更持久的保存数据。浏览器针对它们没有任何默认行为,这样一来,就把保存数据、读取数据的工作交给了前端开发者,这就让恶意攻击者难以针对登录状态进行攻击。 cookie 的大小是有限制的,一般浏览器会限制同一个域下的 cookie 总量为 4M,而 sessionStorage 和 localStorage 则没有限制 cookie 会与 domain、path 关联,而 sessionStorage 和 localStorage 只与 domain 关联

  35. post 请求什么时候用 form data 什么时候用 request payload

    参考答案:

    form data 适合传递简单的键值对信息,由于传递的信息比较扁平,难以传递深层次嵌套的数据

    request payload 适合传递任意格式的数据,包括单个数字、布尔、深层次嵌套的对象、数组等,但 request payload 不适合传递文件数据

    在前后端分离的项目中,对于非文件数据的传递,都推荐使用 request payload 的形式,以传递最明确的数据类型和数据结构,而对于文件上传,则推荐使用传统的 form data

  36. http 常见请求方法有哪些?

    参考答案:

    • GET,表示向服务器获取资源
    • POST,表示向服务器提交信息,通常用于产生新的数据,比如注册
    • PUT,表示希望修改服务器的数据,通常用于修改
    • DELETE,表示希望删除服务器的数据
    • OPTIONS,发生在跨域的预检请求中,表示客户端向服务器申请跨域提交
    • TRACE,回显服务器收到的请求,主要用于测试和诊断
    • CONNECT,用于建立连接管道,通常在代理场景中使用,网页中很少用到
  37. 列举优化网络性能方法

    参考答案:

    • 优化打包体积

      利用一些工具压缩、混淆最终打包代码,减少包体积

    • 多目标打包

      利用一些打包插件,针对不同的浏览器打包出不同的兼容性版本,这样一来,每个版本中的兼容性代码就会大大减少,从而减少包体积

    • 压缩

      现代浏览器普遍支持压缩格式,因此服务端的各种文件可以压缩后再响应给客户端,只要解压时间小于优化的传输时间,压缩就是可行的

    • CDN

      利用 CDN 可以大幅缩减静态资源的访问时间,特别是对于公共库的访问,可以使用知名的 CDN 资源,这样可以实现跨越站点的缓存

    • 缓存

      对于除 HTML 外的所有静态资源均可以开启协商缓存,利用构建工具打包产生的文件 hash 值来置换缓存

    • http2

      开启 http2 后,利用其多路复用、头部压缩等特点,充分利用带宽传递大量的文件数据

    • 雪碧图

      对于不使用 HTTP2 的场景,可以将多个图片合并为雪碧图,以达到减少文件的目的

    • defer、async

      通过 defer 和 async 属性,可以让页面尽早加载 js 文件

    • prefetch、preload

      通过 prefetch 属性,可以让页面在空闲时预先下载其他页面可能要用到的资源

      通过 preload 属性,可以让页面预先下载本页面可能要用到的资源

    • 多个静态资源域

      对于不使用 HTTP2 的场景,将相对独立的静态资源分到多个域中保存,可以让浏览器同时开启多个 TCP 连接,并行下载

  38. session 怎么消除

    参考答案:

    1. 过期时间

      当客户端长时间没有传递 sessionid 过来时,服务器可以在过期时间之后自动清除 session

    2. 客户端主动通知

      可以使用 JS 监听客户端页面关闭或其他退出操作,然后通知服务器清除 session

  39. 什么是 DNS 域名解析?

    参考答案:

    DNS 域名解析是指把域名解析成 IP 地址的过程。

    在具体的实现上,域名解析是由多个层级的服务器共同完成的。在查询域名时,客户端会先检查自身的 DNS 映射表,若找不到解析记录,则使用用户配置的 DNS 服务器,若目标 DNS 服务器中找不到记录,则继续往上一个层级寻找,直到到达根域名服务器,根域名服务器会根据域名的类型,将解析任务分发到对应的子域名服务器依次查找,直到找到解析记录为止。

工程化面试题汇总

  1. 下面的模块导出了什么结果?

    exports.a = 'a';
    module.exports.b = 'b';
    this.c = 'c';
    module.exports = {
      d: 'd'
    }
    

    参考答案:

    { d: 'd' }
    
  2. 说一下你对前端工程化,模块化,组件化的理解?

    参考答案:

    这三者中,模块化是基础,没有模块化,就没有组件化和工程化

    模块化的出现,解决了困扰前端的两大难题:全局污染问题和依赖混乱问题,从而让精细的拆分前端工程成为了可能。

    工程化的出现,解决了前端开发环境和生产环境要求不一致的矛盾。在开发环境中,我们希望代码使用尽可能的细分,代码格式尽可能的统一和规范,而在生产环境中,我们希望代码尽可能的被压缩、混淆,尽可能的优化体积。工程化的出现,就是为了解决这一矛盾,它可以让我们舒服的在开发环境中书写代码,然后经过打包,生成最合适的生产环境代码,这样就解放了开发者的精力,让开发者把更多的注意力集中在开发环境上即可。

    组件化开发是一些前端框架带来的概念,它把一个网页,或者一个站点,甚至一个完整的产品线,划分为多个小的组件,组件是一个可以复用的单元,它包含了一个某个区域的完整功能。这样一来,前端便具备了开发复杂应用的能力。

  3. webpack 和 gulp 的区别是什么?

    直播讲解

    参考答案:

    webpack 是基于模块化的构建工具,gulp 是基于工作流的构建工具。

    webpack 是一个打包器,它以一个入口为起点,构建出整个项目的依赖关系图,然后进行打包合并,生成打包结果。

    gulp 是一个过程管理器,每一步做什么完全看开发人员如何配置,把每一个步骤连接起来形成一个完整的构建流水线。

    这两者并不矛盾,完全可以在一个工程中同时使用 webpack 和 gulp,将 webpack 作为 gulp 流水线中的一环。

  4. webpack 中的 loader 属性和 plugins 属性的区别是什么?

    参考答案:

    它们都是 webpack 功能的扩展点。

    loader 是加载器,主要用于代码转换,比如 JS 代码降级,CSS 预编译、模块化等

    plugins 是插件,webpack 打包流程中每个环节都提供了钩子函数,可以利用这些钩子函数参与到打包生命周期中,更改或增加 webpack 的某些功能,比如生成页面和 css 文件、压缩打包结果等

  5. webpack 的核心概念都有哪些?

    参考答案:

    • loader

      加载器,主要用于代码转换,比如 JS 代码降级,CSS 预编译、模块化等

    • plugin

      插件,webpack 打包流程中每个环节都提供了钩子函数,可以利用这些钩子函数参与到打包生命周期中,更改或增加 webpack 的某些功能,比如生成页面和 css 文件、压缩打包结果等

    • module

      模块。webpack 将所有依赖均视为模块,无论是 js、css、html、图片,统统都是模块

    • entry

      入口。打包过程中的概念,webpack 以一个或多个文件作为入口点,分析整个依赖关系。

    • chunk

      打包过程中的概念,一个 chunk 是一个相对独立的打包过程,以一个或多个文件为入口,分析整个依赖关系,最终完成打包合并

    • bundle

      webpack 打包结果

    • tree shaking

      树摇优化。在打包结果中,去掉没有用到的代码。

    • HMR

      热更新。是指在运行期间,遇到代码更改后,无须重启整个项目,只更新变动的那一部分代码。

    • dev server

      开发服务器。在开发环境中搭建的临时服务器,用于承载对打包结果的访问

  6. commonjs 和 es6 模块的区别是什么?

    参考答案:

    1. CMJ 是社区标准,ESM 是官方标准
    2. CMJ 是使用 API 实现的模块化,ESM 是使用新语法实现的模块化
    3. CMJ 仅在 node 环境中支持,ESM 各种环境均支持
    4. CMJ 是动态的依赖,ESM 既支持动态,也支持静态
    5. ESM 导入时有符号绑定,CMJ 只是普通函数调用和赋值
  7. ES6 中如何实现模块化的异步加载?

    参考答案:

    使用动态导入即可,导入后,得到的是一个 Promise,完成后,得到一个模块对象,其中包含了所有的导出结果。

  8. 说一下 webpack 中的几种 hash 的实现原理是什么?

    参考答案:

    • hash

      hash 是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的 hash 值都会更改,并且全部文件都共用相同的 hash 值

    • chunkhash

      每个打包过程单独的 hash 值,如果一个项目有多个 entry,则每个 entry 维护自己的 chunkhash。

    • contenthash

      每个文件内容单独的 hash 值,它和打包结果文件内容有关,只要文件内容不变,contenthash 不变。

  9. webpack 如果使用了 hash 命名,那是每次都会重新生成 hash 吗?

    参考答案:

    不会。它跟关联的内容是否有变化有关系,如果没有变化,hash 就不会变。具体来说,contenthash 和具体的打包文件内容有关,chunkhash 和某一 entry 为起点的打包过程中涉及的内容有关,hash 和整个工程所有模块内容有关。

  10. webpack 中是如何处理图片的? (抖音直播)

    参考答案:

    webpack 本身不处理图片,它会把图片内容仍然当做 JS 代码来解析,结果就是报错,打包失败。如果要处理图片,需要通过 loader 来处理。其中,url-loader 会把图片转换为 base64 编码,然后得到一个 dataurl,file-loader 则会将图片生成到打包目录中,然后得到一个资源路径。但无论是哪一种 loader,它们的核心功能,都是把图片内容转换成 JS 代码,因为只有转换成 JS 代码,webpack 才能识别。

  11. webpack 打包出来的 html 为什么 style 放在头部 script 放在底部?

    说明:这道题的表述是有问题的,webpack 本身并不打包 html,相反,它如果遇到 html 代码会直接打包失败,因为 webpack 本身只能识别 JS。之所以能够打包出 html 文件,是因为插件或 loader 的作用,其中,比较常见的插件是 html-webpack-plugin。所以这道题的正确表述应该是:「html-webpack-plugin 打包出来的 html 为什么 style 放在头部 script 放在底部?」

    参考答案:

    浏览器在解析 HTML 时是从上到下进行解析的,当遇到样式和 JS 时,都会停止 HTML 解析,转而解析样式和执行 JS。而我们往往希望,页面的样式解析完成后再解析 HTML,这样可以避免页面闪烁,基于此,样式应该放到顶部;而相反的,我们希望在解析完 HTML 后再执行 JS,这样可以让用户尽快的看到页面,同时也让 JS 执行时能够拿到完整的 DOM 树,基于此,JS 代码应该放到底部。

    不过,在 HTML5 中出现了 async 和 defer 属性,使用该属性可以更好的解决 JS 的问题,我们可以把 script 放到顶部,让浏览器尽快下载,但延迟执行。实际上,在新版本的 html-webpack-plugin 中,它已经这样做了。

  12. webpack 配置如何实现开发环境不使用 cdn、生产环境使用 cdn?

    要配置 CDN,有两个步骤:

    1. 在 html 模板中直接加入 cdn 引用
    2. 在 webpack 配置中,加入externals配置,告诉 webpack 不要打包其中的模块,转而使用全局变量

    若要在开发环境中不使用 CDN,只需根据环境变量判断不同的环境,进行不同的打包处理即可。

    1. 在 html 模板中使用 ejs 模板语法进行判断,只有在生产环境中引入 CDN
    2. 在 webpack 配置中,可以根据process.env中的环境变量进行判断是否使用externals配置
    3. package.json脚本中设置不同的环境变量完成打包或开发启动。
  13. 介绍一下 webpack4 中的 tree-shaking 的工作流程?

    推荐阅读:tsejx.github.io/webpack-gui…

    参考答案:

    tree-shaking 仅支持 ESM 的静态导入语法,对于 CMJ 或者 ESM 中的动态导入不支持 tree shaking。

    具体流程主要分为两步:标记和删除

    1. 标记

      webpack 在分析依赖时,会使用注释的方式对导入和导出进行标记,对于模块中没有被其他模块用到的导出标记为 unused harmony export

    2. 删除

      之后在 Uglifyjs (或者其他类似的工具) 步骤进行代码精简,把标记为无用的代码删除。

  14. 说一下 webpack loader 的作用是什么?

    参考答案:

    用于转换代码。有时是因为 webpack 无法识别某些内容,比如图片、css 等,需要由 loader 将其转换为 JS 代码。有时是因为某些代码需要被特殊处理,比如 JS 兼容性的处理,需要由 loader 将其进一步转换。不管是什么情况,loader 的作用只有一个,就是转换代码。

  15. 在开发过程中如果需要对已有模块进行扩展,如何进行开发保证调用方不受影响?

    参考答案:

    实际上就是一个版本管理的问题。

    如果此次模块升级只是修复了某一些 bug,作为补丁版本升级即可,不影响主版本和次版本号

    如果此次模块升级会新增一些内容,完全兼容之前的 API,作为次版本升级即可

    如果此次模块升级会修改之前的 API,则作为主版本升级

    在开发项目时,让项目依赖模块的主版本,因此,当模块更新时,只要不是主版本更新,项目都可以非常方便的升级模块版本,无须改动任何代码。但若涉及主版本更新,项目可以完全无视此次版本更新,仍然使用之前的旧版本,无须改动任何代码;当然也可以升级主版本,但就会涉及代码的改动,这就好比跟将 vue2 升级到 vue3 会涉及大量改动一样。

    而在开发模块时,在一开始就要精心设计 API,尽量保证 API 的接口稳定,不要经常变动主版本号。如果实在要更新主版本,就需要在一段时间内同时维护两个版本(新的主版本,旧的主版本),给予其他项目一定的升级时间。

  16. export 和 export default 的区别是什么?

    参考答案:

    export 为普通导出,又叫做具名导出,顾名思义,它导出的数据必须带有命名,比如变量定义、函数定义这种带有命名的语句。在导出的模块对象中,命名即为模块对象的属性名。在一个模块中可以有多个具名导出

    export default 为默认导出,在模块对象中名称固定为 default,因此无须命名,通常导出一个表达式或字面量。在一个模块中只能有一个默认导出。

  17. webpack 打包原理是什么?

    参考答案:

    1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数
    2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
    3. 确定入口:根据配置中的 entry 找出所有的入口文件
    4. 编译模块:从入口文件出发,调用所有配置的 loader 对模块进行翻译,再把翻译后的内容转换成 AST,通过对 AST 的分析找出该模块依赖的模块,再 递归 本步骤直到所有入口依赖的文件都经过了本步骤的处理
    5. 完成模块编译:在经过第 4 步使用 loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的 依赖关系图
    6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
    7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
  18. webpack 热更新原理是什么?

    参考答案:

    当开启热更新后,页面中会植入一段 websocket 脚本,同时,开发服务器也会和客户端建立 websocket 通信,当源码发生变动时,webpack 会进行以下处理:

    1. webpack 重新打包
    2. webpack-dev-server 检测到模块的变化,于是通过 webscoket 告知客户端变化已经发生
    3. 客户端收到消息后,通过 ajax 发送请求到开发服务器,以过去打包的 hash 值请求服务器的一个 json 文件
    4. 服务器告诉客户端哪些模块发生了变动,同时告诉客户端这次打包产生的新 hash 值
    5. 客户端再次用过去的 hash 值,以 JSONP 的方式请求变动的模块
    6. 服务器响应一个函数调用,用于更新模块的代码
    7. 此时,模块代码已经完成更新。客户端按照之前的监听配置,执行相应模块变动后的回调函数。
  19. 如何优化 webpack 的打包速度?

    参考答案:

    1. noParse

      很多第三方库本身就是已经打包好的代码,对于这种代码无须再进行解析,可以使用 noParse 配置排除掉这些第三方库

    2. externals

      对于一些知名的第三方库可以使用 CDN,这部分库可以通过 externals 配置不进行打包

    3. 限制 loader 的范围

      在使用 loader 的时候,可以通过 exclude 排除掉一些不必要的编译,比如 babel-loader 对于那些已经完成打包的第三方库没有必要再降级一次,可以排除掉

    4. 开启 loader 缓存

      可以利用cache-loader缓存 loader 的编译结果,避免在源码没有变动时反复编译

    5. 开启多线程编译

      可以利用thread-loader开启多线程编译,提升编译效率

    6. 动态链接库

      对于某些需要打包的第三方库,可以使用 dll 的方式单独对其打包,然后 DLLPlugin 将其整合到当前项目中,这样就避免了在开发中频繁去打包这些库

  20. webpack 如何实现动态导入?

    参考答案:

    当遇到代码中包含动态导入语句时,webpack 会将导入的模块及其依赖分配到单独的一个 chunk 中进行打包,形成单独的打包结果。而动态导入的语句会被编译成一个普通的函数调用,该函数在执行时,会使用 JSONP 的方式动态的把分离出去的包加载到模块集合中。

  21. 说一下 webpack 有哪几种文件指纹

    参考答案:

    • hash

      hash 是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的 hash 值都会更改,并且全部文件都共用相同的 hash 值

    • chunkhash

      每个打包过程单独的 hash 值,如果一个项目有多个 entry,则每个 entry 维护自己的 chunkhash。

    • contenthash

      每个文件内容单独的 hash 值,它和打包结果文件内容有关,只要文件内容不变,contenthash 不变。

  22. 常用的 webpack Loader 都有哪些?

    参考答案:

    • cache-loader:启用编译缓存
    • thread-loader:启用多线程编译
    • css-loader:编译 css 代码为 js
    • file-loader:保存文件到输出目录,将文件内容转换成文件路径
    • postcss-loader:将 css 代码使用 postcss 进行编译
    • url-loader:将文件内容转换成 dataurl
    • less-loader:将 less 代码转换成 css 代码
    • sass-loader:将 sass 代码转换成 css 代码
    • vue-loader:编译单文件组件
    • babel-loader:对 JS 代码进行降级处理
  23. 说一下 webpack 常用插件都有哪些?

    参考答案:

    • clean-webpack-plugin:清除输出目录
    • copy-webpack-plugin:复制文件到输出目录
    • html-webpack-plugin:生成 HTML 文件
    • mini-css-extract-plugin:将 css 打包成单独文件的插件
    • HotModuleReplacementPlugin:热更新的插件
    • purifycss-webpack:去除无用的 css 代码
    • optimize-css-assets-webpack-plugin:优化 css 打包体积
    • uglify-js-plugin:对 JS 代码进行压缩、混淆
    • compression-webpack-plugin:gzip 压缩
    • webpack-bundle-analyzer:分析打包结果
  24. 使用 babel-loader 会有哪些问题,可以怎样优化?

    参考答案:

    1. 如果不做特殊处理,babel-loader 会对所有匹配的模块进行降级,这对于那些已经处理好兼容性问题的第三方库显得多此一举,因此可以使用 exclude 配置排除掉这些第三方库
    2. 在旧版本的 babel-loader 中,默认开启了对 ESM 的转换,这样会导致 webpack 的 tree shaking 失效,因为 tree shaking 是需要保留 ESM 语法的,所以需要关闭 babel-loader 的 ESM 转换,在其新版本中已经默认关闭了。
  25. babel 是如何对 class 进行编译的?

    参考答案:

    本质上就是把 class 语法转换成普通构造函数定义,并做了以下处理:

    1. 增加了对 this 指向的检测
    2. 将原型方法和静态方法变为不可枚举
    3. 将整个代码放到了立即执行函数中,运行后返回构造函数本身
  26. 解释一下 babel-polyfill 的作用是什么?

    说明:

    babel-polyfill 已经是一个非常古老的项目了,babel 从 7.4 版本开始已不再支持它,转而使用更加强大的 core-js,此题也适用于问「core-js 的作用是什么」

    参考答案:

    默认情况下,babel 本身只转换新的语法,而不对新的 API 进行处理。由于旧的环境中无法支持新的 API,比如 IE6 无法支持 fetch api,这就需要一个补丁,用旧语言的特性实现一遍新的 API,babel-polyfill 就是用来做这件事的。

  27. 解释一下 less 的&的操作符是做什么用的?

    参考答案:

    &符号后面的内容会和父级选择器合并书写,即中间不加入空格字符

  28. 在前端工程化中,可以进行哪些方面的优化?

    参考答案:

    1. 对传输性能的优化

      • 压缩和混淆

        使用 Uglifyjs 或其他类似工具对打包结果进行压缩、混淆,可以有效的减少包体积

      • tree shaking

        项目中尽量使用 ESM,可以有效利用 tree shaking 优化,降低包体积

      • 抽离公共模块

        将一些公共代码单独打包,这样可以充分利用浏览器缓存,其他代码变动后,不影响公共代码,浏览器可以直接从缓存中找到公共代码。

        具体方式有多种,比如 dll、splitChunks

      • 异步加载

        对一些可以延迟执行的模块可以使用动态导入的方式异步加载它们,这样在打包结果中,它们会形成单独的包,同时,在页面一开始解析时并不需要加载它们,而是页面解析完成后,执行 JS 的过程中去加载它们。

        这样可以显著提高页面的响应速度,在单页应用中尤其有用。

      • CDN

        对一些知名的库使用 CDN,不仅可以节省打包时间,还可以显著提升库的加载速度

      • gzip

        目前浏览器普遍支持 gzip 格式,因此可以将静态文件均使用 gzip 进行压缩

      • 环境适配

        有些打包结果中包含了大量兼容性处理的代码,但在新版本浏览器中这些代码毫无意义。因此,可以把浏览器分为多个层次,为不同层次的浏览器给予不同的打包结果。

    2. 对打包过程的优化

      • noParse

        很多第三方库本身就是已经打包好的代码,对于这种代码无须再进行解析,可以使用 noParse 配置排除掉这些第三方库

      • externals

        对于一些知名的第三方库可以使用 CDN,这部分库可以通过 externals 配置不进行打包

      • 限制 loader 的范围

        在使用 loader 的时候,可以通过 exclude 排除掉一些不必要的编译,比如 babel-loader 对于那些已经完成打包的第三方库没有必要再降级一次,可以排除掉

      • 开启 loader 缓存

        可以利用cache-loader缓存 loader 的编译结果,避免在源码没有变动时反复编译

      • 开启多线程编译

        可以利用thread-loader开启多线程编译,提升编译效率

      • 动态链接库

        对于某些需要打包的第三方库,可以使用 dll 的方式单独对其打包,然后 DLLPlugin 将其整合到当前项目中,这样就避免了在开发中频繁去打包这些库

    3. 对开发体验的优化

      • lint

        使用 eslint、stylelint 等工具保证团队代码风格一致

      • HMR

        使用热替换避免页面刷新导致的状态丢失,提升开发体验

  29. 如果有一个工程打包特别大-如何进行优化?

    参考答案:

    1. CDN

      如果工程中使用了一些知名的第三方库,可以考虑使用 CDN,而不进行打包

    2. 抽离公共模块

      如果工程中用到了一些大的公共库,可以考虑将其分割出来单独打包

    3. 异步加载

      对于那些不需要在一开始就执行的模块,可以考虑使用动态导入的方式异步加载它们,以尽量减少主包的体积

    4. 压缩、混淆

    5. tree shaking

      尽量使用 ESM 语法进行导入导出,充分利用 tree shaking 去除无用代码

    6. gzip

      开启 gzip 压缩,进一步减少包体积

    7. 环境适配

      有些打包结果中包含了大量兼容性处理的代码,但在新版本浏览器中这些代码毫无意义。因此,可以把浏览器分为多个层次,为不同层次的浏览器给予不同的打包结果。

  30. webpack 怎么进行首屏加载的优化?

    参考答案:

    1. CDN

      如果工程中使用了一些知名的第三方库,可以考虑使用 CDN,而不进行打包

    2. 抽离公共模块

      如果工程中用到了一些大的公共库,可以考虑将其分割出来单独打包

    3. 异步加载

      对于那些不需要在一开始就执行的模块,可以考虑使用动态导入的方式异步加载它们,以尽量减少主包的体积

    4. 压缩、混淆

    5. tree shaking

      尽量使用 ESM 语法进行导入导出,充分利用 tree shaking 去除无用代码

    6. gzip

      开启 gzip 压缩,进一步减少包体积

    7. 环境适配

      有些打包结果中包含了大量兼容性处理的代码,但在新版本浏览器中这些代码毫无意义。因此,可以把浏览器分为多个层次,为不同层次的浏览器给予不同的打包结果。

  31. 介绍一下 webpack scope hoisting?

    参考答案:

    scope hoisting 是 webpack 的内置优化,它是针对模块的优化,在生产环境打包时会自动开启。

    在未开启scope hoisting时,webpack 会将每个模块的代码放置在一个独立的函数环境中,这样是为了保证模块的作用域互不干扰。

    而 scope hoisting 的作用恰恰相反,是把多个模块的代码合并到一个函数环境中执行。在这一过程中,webpack 会按照顺序正确的合并模块代码,同时对涉及的标识符做适当处理以避免重名。

    这样做的好处是减少了函数调用,对运行效率有一定提升,同时也降低了打包体积。

    但 scope hoisting 的启用是有前提的,如果遇到某些模块多次被其他模块引用,或者使用了动态导入的模块,或者是非 ESM 的模块,都不会有 scope hoisting。

  32. webpack proxy 工作原理,为什么能解决跨域?

    说明:

    严格来说,webpack 只是一个打包工具,它并没有 proxy 的功能,甚至连服务器的功能都没有。之所以能够在 webpack 中使用 proxy 配置,是因为它的一个插件,即 webpack-dev-server 的能力。

    所以,此题应该问做:「webpack-dev-server 工作原理,为什么能解决跨域?」

    参考答案:

    首先,proxy 配置是针对开发环境的,对生产环境没有任何意义。

    当我们通过 webpack-dev-server 启动开发服务器后,所有的打包资源都可以通过访问开发服务器获得。

    与此同时,我们又配置了 proxy,当我们向开发服务器请求特定的地址时,开发服务器会将其代理到目标地址。因此,后续对代理地址的请求,就可以变为直接请求开发服务器。

    这样一来,我们请求静态页面的域和请求代理地址的域就同源了,因为都是请求开发服务器,所以就不会产生跨域问题。

  33. 组件发布的是不是所有依赖这个组件库的项目都需要升级?

    参考答案:

    实际上就是一个版本管理的问题。

    如果此次模块升级只是修复了某一些 bug,作为补丁版本升级即可,不影响主版本和次版本号

    如果此次模块升级会新增一些内容,完全兼容之前的 API,作为次版本升级即可

    如果此次模块升级会修改之前的 API,则作为主版本升级

    在开发项目时,让项目依赖模块的主版本,因此,当模块更新时,只要不是主版本更新,项目都可以非常方便的升级模块版本,无须改动任何代码。但若涉及主版本更新,项目可以完全无视此次版本更新,仍然使用之前的旧版本,无须改动任何代码;当然也可以升级主版本,但就会涉及代码的改动,这就好比跟将 vue2 升级到 vue3 会涉及大量改动一样。

    而在开发模块时,在一开始就要精心设计 API,尽量保证 API 的接口稳定,不要经常变动主版本号。如果实在要更新主版本,就需要在一段时间内同时维护两个版本(新的主版本,旧的主版本),给予其他项目一定的升级时间。

  34. 开发过程中,如何进行公共组件的设计?(字节跳动)

    参考答案:

    1. 确定使用场景

      明确这个公共组件的需求是怎么产生的,它目前的使用场景有哪些,将来还可能出现哪些使用场景。

      明确使用场景至关重要,它决定了这个组件的使用边界在哪,通用到什么程度,从而决定了这个组件的开发难度

    2. 设计组件功能

      根据其使用场景,设计出组件的属性、事件、使用说明文档

    3. 测试用例

      根据使用说明文档编写组件测试用例

    4. 完成开发

      根据使用说明文档、测试用例完成开发

  35. 说一下项目里有做过哪些 webpack 上的优化(字节跳动)

    参考答案:

    1. 对传输性能的优化

      • 压缩和混淆

        使用 Uglifyjs 或其他类似工具对打包结果进行压缩、混淆,可以有效的减少包体积

      • tree shaking

        项目中尽量使用 ESM,可以有效利用 tree shaking 优化,降低包体积

      • 抽离公共模块

        将一些公共代码单独打包,这样可以充分利用浏览器缓存,其他代码变动后,不影响公共代码,浏览器可以直接从缓存中找到公共代码。

        具体方式有多种,比如 dll、splitChunks

      • 异步加载

        对一些可以延迟执行的模块可以使用动态导入的方式异步加载它们,这样在打包结果中,它们会形成单独的包,同时,在页面一开始解析时并不需要加载它们,而是页面解析完成后,执行 JS 的过程中去加载它们。

        这样可以显著提高页面的响应速度,在单页应用中尤其有用。

      • CDN

        对一些知名的库使用 CDN,不仅可以节省打包时间,还可以显著提升库的加载速度

      • gzip

        目前浏览器普遍支持 gzip 格式,因此可以将静态文件均使用 gzip 进行压缩

      • 环境适配

        有些打包结果中包含了大量兼容性处理的代码,但在新版本浏览器中这些代码毫无意义。因此,可以把浏览器分为多个层次,为不同层次的浏览器给予不同的打包结果。

    2. 对打包过程的优化

      • noParse

        很多第三方库本身就是已经打包好的代码,对于这种代码无须再进行解析,可以使用 noParse 配置排除掉这些第三方库

      • externals

        对于一些知名的第三方库可以使用 CDN,这部分库可以通过 externals 配置不进行打包

      • 限制 loader 的范围

        在使用 loader 的时候,可以通过 exclude 排除掉一些不必要的编译,比如 babel-loader 对于那些已经完成打包的第三方库没有必要再降级一次,可以排除掉

      • 开启 loader 缓存

        可以利用cache-loader缓存 loader 的编译结果,避免在源码没有变动时反复编译

      • 开启多线程编译

        可以利用thread-loader开启多线程编译,提升编译效率

      • 动态链接库

        对于某些需要打包的第三方库,可以使用 dll 的方式单独对其打包,然后 DLLPlugin 将其整合到当前项目中,这样就避免了在开发中频繁去打包这些库

    3. 对开发体验的优化

      • lint

        使用 eslint、stylelint 等工具保证团队代码风格一致

      • HMR

        使用热替换避免页面刷新导致的状态丢失,提升开发体验

  36. 具体说一下 splitchunksplugin 的使用场景及使用方法。(字节跳动)

    参考答案:

    1. 公共模块

      比如某些多页应用会有多个入口,从而形成多个 chunk,而这些 chunk 中用到了一些公共模块,为了减少整体的包体积,可以使用 splitchunksplugin 将公共模块分离出来。

      可以配置 minChunks 来指定被多少个 chunk 引用时进行分包

    2. 并行下载

      由于 HTML5 支持 defer 和 async,因此可以同时下载多个 JS 文件以充分利用带宽。如果打包结果是一个很大的文件,就无法利用到这一点。

      可以利用 splitchunks 插件将文件进行拆分,通过配置 maxSize 属性指定包体积达到多大时进行拆分

  37. 描述一下 webpack 的构建流程?(CVTE)

    参考答案:

    1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数
    2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译
    3. 确定入口:根据配置中的 entry 找出所有的入口文件
    4. 编译模块:从入口文件出发,调用所有配置的 loader 对模块进行翻译,再把翻译后的内容转换成 AST,通过对 AST 的分析找出该模块依赖的模块,再 递归 本步骤直到所有入口依赖的文件都经过了本步骤的处理
    5. 完成模块编译:在经过第 4 步使用 loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的 依赖关系图
    6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
    7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
  38. 解释一下 webpack 插件的实现原理?(CVTE)

    参考答案:

    本质上,webpack 的插件是一个带有apply函数的对象。当 webpack 创建好 compiler 对象后,会执行注册插件的 apply 函数,同时将 compiler 对象作为参数传入。

    在 apply 函数中,开发者可以通过 compiler 对象监听多个钩子函数的执行,不同的钩子函数对应 webpack 编译的不同阶段。当 webpack 进行到一定阶段后,会调用这些监听函数,同时将 compilation 对象传入。开发者可以使用 compilation 对象获取和改变 webpack 的各种信息,从而影响构建过程。

  39. 有用过哪些插件做项目的分析吗?(CVTE)

    参考答案:

    用过 webpack-bundle-analyzer 分析过打包结果,主要用于优化项目打包体积

  40. 什么是 babel,有什么作用?

    参考答案:

    babel 是一个 JS 编译器,主要用于将下一代的 JS 语言代码编译成兼容性更好的代码。

    它其实本身做的事情并不多,它负责将 JS 代码编译成为 AST,然后依托其生态中的各种插件对 AST 中的语法和 API 进行处理

  41. 解释一下 npm 模块安装机制是什么?

    参考答案:

    1. npm 会检查本地的 node_modules 目录中是否已经安装过该模块,如果已经安装,则不再重新安装
    2. npm 检查缓存中是否有相同的模块,如果有,直接从缓存中读取安装
    3. 如果本地和缓存中均不存在,npm 会从 registry 指定的地址下载安装包,然后将其写入到本地的 node_modules 目录中,同时缓存起来。