Skip to content

喜马拉雅

http1 和 http2 和 http3.0

问题:请分别介绍一下 http1、http2 和 http3

回答:http1.0 每次请求需要建立一个新的连接,进行四次握手,连接无法复用;http1.1 在此基础上升级为支持长连接,加入管道化很断点续传,但依然存在不支持服务端推送、产生队头阻塞问题和明文传输问题;http2.0 是采用了二进制分帧和头部压缩,也实现了服务端推送,解决了 http1.1 头部过大和队头阻塞的问题;http3.0 采用 quik 协议实现了多路复用流量控制等功能。

ps: 当时的话 2.0 只记得二进制分帧,3.0 直接懵了不会

详解

什么是 http

HTTP(HyperText Transfer Protocol),是超文本传输协议,是目前互联网上要用最广泛的一种网络协议,所有的 WWW 文件都必须遵守该标准。HTTP 又使用了可靠的数据传输协议 TCP 协议,不会产生数据丢失和损坏。(HTTP 协议属于应用层协议)

工作流程为:

  • 浏览器与服务器建立 TCP 连接,即三次握手
  • TCP 连接成功,浏览器发出 HTTP 请求命令
  • 服务端接收请求并返回 HTTP 响应
  • 服务器关闭连接,即四次挥手
  • 浏览器解析请求的资源

http1.0

image.png
特点
  • 每次请求都必须新建一次连接,必须通过 TCP 的三次握手才能开始传输数据,连接结束之后还要经历四次挥手。
  • 不跟踪每个浏览器的历史请求
缺点
  • 连接无法复用:每次请求都需要建立一个 TCP 连接,费时费力

  • 队头阻塞:下一个请求必须在前一个请求响应到达后发送。如果某请求一直不到达,那么下一个请求就一直不发送。(高延迟--带来页面加载速度的降低)

  • 每下载文件时都需要建立 TCP 连接、传输数据和断开连接这样的步骤,无疑会增加大量无谓的开销,因此 HTTP1.1 增加了持久连接的方法。

http1.1

在 HTTP1.1 中,默认支持长连接,即在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟

特点
  • 支持长连接,通过 Keep-Alive 保持 HTTP 连接不断开,避免重复建立 TCP 连接
  • 管道化,通过长连接实现在一个连接中传输多个文件
  • 加入缓存处理(新字段 cache-control)
  • 支持断点续传
  • 增加了 Host 字段,实现了在一台 WEB 服务器上可以同一个 IP 地址和端口号上使用不同的主机名来创建多个虚拟 WEB 站点
  • 并且添加了其他请求方法:put、delete、options...
缺点
  • 高延迟--队头阻塞

  • 无状态特性--阻碍交互

  • (带来巨大头部)协议对于连接状态没有记忆能力。纯净的 HTTP 是没有 cookie 等机制的,每一个连接都是一个新的连接。上一次请求验证了用户名密码,而下一次请求服务器并不知道它与上一条请求有何关联

  • 明文传输--不安全

  • 传输内容没有加密,中途可能被篡改和劫持

  • 不支持服务端推送

在 HTTP2 之前 Google 对 HTTP1.1 进行了改良:SPDY 协议(二进制分帧层)

  • 多路复用--解决队头阻塞

    • SPDY 允许一个连接上无限制并发流。因为请求在一个通道上,TCP 效率更高 (慢启动),更少的网络连接,发出更密集的包。
  • 头部压缩--解决巨大的 HTTP 头部

    • 使用专门的 HPACK 算法,每次请求和响应只发送 差异头部,一般可以达到 50%~90%的高压缩率。
  • 请求优先级--先获取重要数据

    • 虽然无限的并发流解决了队头阻塞的问题,但是如果带宽受限,客户端可能会因防止堵塞通道而阻止请求。在网络通道被非关键资源堵塞时,高优先级的请求会被优先处理。
  • 服务器推送--填补空缺

    • 可以让服务端主动把资源文件推送给客户端。当然客户端也有权力选择是否接收。
  • 提高安全性

    • 支持使用 HTTPS 进行加密传输。

http2.0

HTTP2 是基于 SPDY,最大的一个目标是在用户和网站键只用一个连接 。

HTTP2 采用一个域名只使用一个 TCP 长连接来传输数据,这样整个页面资源的下载过程只需一次慢启动,也避免了多个 TCP 连接竞争带宽。同时实现资源的并行请求,解决队头阻塞问题。

特点
  • 二进制传输:将请求和响应数据分为更小的帧,并且采用二进制编码

  • Header 压缩:采用 HPACK 算法压缩头部,同时同一个域名下的两个请求,只会发送差异数据,减少冗余的数据传输,降低开销

  • 多路复用:同一个域名下所有通信都是单个连接上完成,单个连接可以承载任意数量的双向数据流,数据流以消息形式发送,而消息由一个或多个帧组成,可以乱序发送

  • 服务端推送:服务端可以新建“流”主动向客户端发送消息,提前推送客户端需要的静态资源,减少等待延迟

  • 提高安全性:HTTP2 也是明文的,只不过格式是二进制的,但 HTTP2 都是 https 协议的,跑在 TSL 上面。

问题
  • TCP 以及 TCP+TLS 建立连接的延时
  • TCP 的队头阻塞并没有彻底解决
    • 一旦遇到丢包,TCP 协议还是会重新发送数据。我们知道在 HTTP/2 中,多个请求是跑在一个 TCP 管道中的,如果其中任意一路数据流中出现了丢包的情况,那么就会阻塞该 TCP 连接中的所有请求。这不同于 HTTP/1.1,使用 HTTP/1.1 时,浏览器为每个域名开启了 6 个 TCP 连接,如果其中的 1 个 TCP 连接发生了队头阻塞,那么其他的 5 个连接依然可以继续传输数据。
  • 多路复用导致服务器压力上升
  • 多路复用容易 Timeout

http3.0

HTTP3 甩掉 TCP、TSL 的包袱,构建高效网络 QUIC 协议。

HTTP3 选择了 UDP 协议,基于 UDP 实现了类似 TCP 的多路数据流、传输可靠性等功能,将这套功能称为 QUIC 协议。

特点
  • 基于 UDP 协议改造,实现了快速握手

  • 集成了 TLS 的加密功能

  • 多路复用,彻底解决了头阻塞问题(一个物理连接上可以有多个独立的逻辑数据流,实现了数据流的单独传输)

  • 实现了类似 TCP 的流量控制、传输可靠性的功能

https 和 http

问:https 与 http 有什么区别,解决了什么问题

回答:增加了加密处理、认证机制和完整性保护,引入 ssl 加密传输,解决了明文传输带来的安全性问题

详解

  • HTTPS 只是在 HTTP 的基础之上增加了加密处理、认证机制和完整性保护,即 HTTPS = HTTP + 加密 + 认证 + 完整性保护
  • 客户使用 https 的 URL 访问 Web 服务器,要求与 Web 服务器建立 SSL 连接。
  • Web 服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
  • 客户端的浏览器与 Web 服务器开始协商 SSL 连接的安全等级,也就是信息加密的等级。
  • 客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
  • Web 服务器利用自己的私钥解密出会话密钥。
  • Web 服务器利用会话密钥加密与客户端之间的通信。

http 常见状态码及意义

  • 200 正确
  • 404 无法找到资源
  • 500 未知错误
  • 501 服务端不支持请求功能
  • 300 多种选择
  • 301url 永久重定向
  • 302 url 临时重定向
  • 304 请求资源未修改
  • 400 服务器不理解请求的语法
  • 401 请求要求进行用户身份证
  • 403 服务器理解请求客户端的请求,但是拒绝执行此请求

详解

  • 1xx信息性状态码,表示服务器正在处理请求,请求进行中

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

    • 200(成功):请求已成功,请求所希望的响应头或数据体将随此响应返回
    • 201(已创建):请求成功并且服务器创建了新的资源
    • 202(已创建):服务器已经接收请求,但尚未处理完成
    • 203(非授权信息):服务器已成功处理请求,但返回的信息可能来自另一来源
    • 204(无内容):服务器成功处理请求,但没有返回任何内容
    • 205(重置内容):服务器成功处理请求,但没有返回任何内容
    • 206(部分内容):表示服务器成功处理了部分请求,通常在断点续传或分块下载时使用
  • 3xx重定向状态码,跳转状态,表示需要进一步的操作以完成请求

    • 300(多种选择):针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择
    • 301(永久移动):请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置
    • 302(临时移动): 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求
    • 303(查看其他位置):请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码
    • 304(协商缓存):服务器通过返回状态码 304 可以告诉客户端请求资源成功,但是这个资源不是由服务器提供返回给客户端的,而是客户端本地浏览器缓存中就有的这个资源,因为可以从缓存中获取这个资源,从而节省传输的开销。(也有可能是前端没有配置nginx代理)
    • 305 (使用代理): 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理
    • 307 (临时重定向): 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求
  • 4xx客户端错误状态码,表示请求包含错误无法完成

    • 400(错误请求): 服务器不理解请求的语法
    • 401(未授权): 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。
    • 403(禁止): 服务器拒绝请求
    • 404(未找到): 请求的资源不存在
    • 405(方法禁用): 禁用请求中指定的方法
    • 406(不接受): 无法使用请求的内容特性响应请求的网页
    • 407(需要代理授权): 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理
    • 408(请求超时): 服务器等候请求时发生超时
  • 5xx服务器错误状态码,表示服务器在处理请求时发生了错误

    • 500(服务器内部错误):服务器遇到错误,无法完成请求
    • 501(尚未实施):服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码
    • 502(错误网关): 服务器作为网关或代理,从上游服务器收到无效响应
    • 503(服务不可用): 服务器目前无法使用(由于超载或停机维护)
    • 504(网关超时): 服务器作为网关或代理,但是没有及时从上游服务器收到请求
    • 505(HTTP 版本不受支持): 服务器不支持请求中所用的 HTTP 协议版本

js 的数组使用方法

问:你常用哪些 js 数组的方法

回答:map、forEach、filter、join、push、pop 等

详解

详细见:超级详细的 js 数组方法详解 - 掘金 (juejin.cn)

  • join():用指定的分隔符将数组每一项拼接为字符串
  • push() :向数组的末尾添加新元素
  • pop():删除数组的最后一项
  • shift():删除数组的第一项
  • unshift():向数组首位添加新元素
  • slice():按照条件查找出其中的部分元素
  • splice():对数组进行增删改
  • fill(): 方法能使用特定值填充数组中的一个或多个元素
  • filter():“过滤”功能
  • concat():用于连接两个或多个数组
  • indexOf():检测当前值在数组中第一次出现的位置索引
  • lastIndexOf():检测当前值在数组中最后一次出现的位置索引
  • every():判断数组中每一项都是否满足条件
  • some():判断数组中是否存在满足条件的项
  • includes():判断一个数组是否包含一个指定的值
  • sort():对数组的元素进行排序
  • reverse():对数组进行倒序
  • forEach():ES5 及以下循环遍历数组每一项
  • map():ES6 循环遍历数组每一项
  • copyWithin():用于从数组的指定位置拷贝元素到数组的另一个指定位置中
  • find():返回匹配的值
  • findIndex():返回匹配位置的索引
  • toLocaleString()、toString():将数组转换为字符串
  • flat()、flatMap():扁平化数组
  • entries() 、keys() 、values():遍历数组

如何保证代码风格高度统一

问: 如何保持前端代码风格高度统一

答:eslint、prettier 来进行代码检查并格式化,通过 commitizen、husky 规范 git 提交并且在每次提交前进行 eslint 检查和 prettier 格式化

git 使用情况

问: 你的 git 使用情况怎么样

答: 基本上所写的项目都有用 git 进行版本管理,在公司实习时也有使用。

编译方面-ast 树

你对 ast 有没有了解

答:抽象语法树,了解不太深

ps:当时真没咋了解

详解

详细解析可以看:前端工程化基石 -- AST(抽象语法树)以及 AST 的广泛应用 🔥 - 掘金 (juejin.cn)

  • AST: Abstract Syntax Tree,抽象语法树,是源代码结构的一种抽象表示,它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

  • 在 Vue 中,抽象语法树主要就是对模板进行了词法分析和语法分析就生成了抽象语法树(其实就是个 js 的对象),然后进行了语义分析等之后便于转化为 js 代码,相当于过渡态吧。

  • 后续的话我应该会专门写一篇文章讲这个

抓包工具

问:有没有用过抓包工具

回答:没有

ps:真没用过。。

详解

抓包就是将网络传输发送与接收的数据包进行截获、重发、编辑、转存等操作

确实没用过,详解可以看这篇文章:前端人必须掌握的抓包技能 - 掘金 (juejin.cn)

webpack

问:用过 webpack 吗,可以讲讲 webpack 吗

回答:webpack 是前端的打包工具,可以将 vue 和 react 等主流框架打包成静态资源部署到服务器上,而且可以解决浏览器的兼容问题。

详解

详解看:[万字总结] 一文吃透 Webpack 核心原理 - 掘金 (juejin.cn)

Webpack 最核心的功能:将各种类型的资源,包括图片、css、js 等,转译、组合、拼接、生成 JS 格式的 bundler 文件,在这个过程核心完成了 内容转换 + 资源合并 两种功能,实现上包含三个阶段:

  1. 初始化阶段:
    1. 初始化参数:从配置文件、 配置对象、Shell 参数中读取,与默认配置结合得出最终的参数
    2. 创建编译器对象:用上一步得到的参数创建 Compiler 对象
    3. 初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化 RuleSet 集合、加载配置的插件等
    4. 开始编译:执行 compiler 对象的 run 方法
    5. 确定入口:根据配置中的 entry 找出所有的入口文件,调用 compilition.addEntry 将入口文件转换为 dependence 对象
  2. 构建阶段:
    1. 编译模块(make):根据 entry 对应的 dependence 创建 module 对象,调用 loader 将模块转译为标准 JS 内容,调用 JS 解释器将内容转换为 AST 对象,从中找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
    2. 完成模块编译:上一步递归处理所有能触达到的模块后,得到了每个模块被翻译后的内容以及它们之间的 依赖关系图
  3. 生成阶段:
    1. 输出资源(seal):根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
    2. 写入文件系统(emitAssets):在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

webpack 和 vite 的区别以及 vite 快的原因

问:webpack 和 vit 有什么区别,vite 为什么会更快

答:webpack 核心本质上是模块打包,首先把各个模块通过编译打包到 bundle 里,然后再执行。所以一旦模块过多,编译的时间就会过长,而且修改一个模块,全部模块重新编译,影响开发体验。而 vite 采用 依赖预构建es Module按需编译等方式,速度相比 webpack 提升非常快。

目前 vite 它只在开发环境会使用esbuildes Module,生产环境还是使用rollup打包。

详解

esbuild

  • 依赖预构建,vite 是采用esbuild打包工具,它在启动时把依赖先转换成 ES 模块,智能导入,依赖项缓存到 node_modules/.vite文件夹,同时浏览器也会缓存。

  • esbuild 采用Go 语言开发,可以多线程并发直接编译成机器码等,它的处理时间比 webpack 这些 js 打包器快很多。

  • vite 还使用 esbuild 来处理ts,jsx等文件的转译,速度也是非常快。

es Module

同时 vite 利用浏览器已经支持es Module的特性,vite 提供ES 模块,浏览器直接加载运行,如果是commonjs模块或者UMD模块,则转换成ES 模块再加载,因为浏览器直接加载ES 模块,速度比还需要编译成 bundle 的 webpack 快得多。

按需编译

vite 只在浏览器需要加载模块时才加载,其它没引用到的不会加载。同时,它的热更新只针对编辑的模块,没有编辑的模块不会热更新,从而快速提升开发体验。

函数闭包

问:讲一下函数闭包

答:在一个作用域中可以访问另一个函数内部的局部变量的函数。

详解

闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。

js
function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

闭包的实现,实际上是利用了JavaScript中作用域链的概念,简单理解就是:在JavaScript中,如果在某个作用域下访问某个变量的时候,如果不存在,就一直向外层寻找,直到在全局作用域下找到对应的变量为止,这里就形成了所谓的作用域链。

闭包的特性

  • 闭包可以访问到父级函数的变量
  • 访问到父级函数的变量不会销毁
js
function person() {
  var age = 18;
  return function () {
    age++;
    console.log(age);
  };
}

let getPersonAge = person();
getPersonAge(); // 19
getPersonAge(); // 20
getPersonAge(); // 21

每当调用getPersonAge()函数的时候,首先要获取age变量,因为JavaScript中存在作用域链的关系,所以会从person函数下得到对应的age,因为闭包存在着闭包可以访问到父级函数的变量,且该变量不会销毁的特性所以上次的变量会被保留下来,所以可以做到自增的实现

闭包的使用

其实如果做前端项目做的多的话,就会发现很多 hooks 基本上就是利用闭包的特性,例如 vueuse 里面的 useWindowScroll,下面分享一个我写的闭包的使用,其实就是实现一个打字效果:

js
import { waitMillis } from "@/utils/time-utils";
import { ref } from "vue";

export function useTypingText(
  textArr: string[],
  tyingSpeed = 300,
  nextWait = 500,
  endWait = 2000
) {
  const typingText = ref("");
  let lastText = "";
  let index = 0;
  let allText = textArr;
  let arrIdx = 0;
  let flag = true;

  async function setAllText(textArr: string[]) {
    allText = textArr;
    arrIdx = 0;
    lastText = "";
    index = 0;
    typingText.value = "";

    flag = false;
    await waitMillis(endWait);
    flag = true;
    typeText();
  }

  async function typeText() {
    while (flag) {
      if (index > allText[arrIdx].length) {
        arrIdx++;
        if (arrIdx === allText.length) {
          typingText.value = typingText.value;
          await waitMillis(endWait);
          lastText = "";
          arrIdx = 0;
        } else {
          typingText.value = typingText.value + "<br>";
          lastText = typingText.value;
          await waitMillis(nextWait);
        }
        index = 0;
      }
      typingText.value = lastText + allText[arrIdx].substring(0, index);
      index++;
      await waitMillis(tyingSpeed); // 调整打字速度
    }
  }

  typeText();
  return {
    typingText,
    setAllText,
  };
}

作用域

问:讲一下作用域

答:作用域指的是一个变量的作用范围。通俗来说,就是你定义的变量并不是在代码的每个地方都能拿到的,而限定你是否能拿到你想要的变量就是作用域。

详解

在 JavaScript 中,作用域分为全局作用域和函数作用域以及块级作用域(ES6 开始有了)。其作用是为了隔离变量,不同作用域下同名变量不会发生冲突。

写一个代码大家看一下应该就懂了

js
// 全局作用域
a = 1;
var b = 2;
let c = 3;
if (true) {
  console.log("a =" + a); // 全局变量在判断语句中能调用
}
function foo() {
  console.log("b =" + b); // 全局变量在函数中也能调用
}
foo();
console.log("c =" + c);
// 全局作用域

// 块级作用域
function foo() {
  // 函数作用域
  let c = true;
  console.log(a);
  // 函数作用域
}
foo(); // 可以输出c
console.log(c); //报错! c is not defined!
// 块级作用域

深拷贝和浅拷贝

问:讲一下深拷贝和浅拷贝,讲一下怎么实现

答:由于 js 特性,我们直接将一个对象赋值给另一个对象时候实际上就是把引用地址复制了一份,这样子一方修改另一方也会修改,这就是浅拷贝;深拷贝就是将该对象的值全部拷贝形成一个副本,二者修改不会相互影响。实现的话常用就是 JSON.parse(JSON.stringify(obj))

详解

在 JavaScript 中,因为对象和数组是引用类型,所以当我们直接将它们赋值给另一个变量时,实际上是将它们的引用地址复制了一份。这样,当我们对其中一个变量进行修改时,另一个变量也会受到影响。因此,为了避免这种情况,我们通常需要使用拷贝方法来复制一个对象或数组的值并创建一个新的副本。本文将来介绍浅拷贝和深拷贝的概念以及它们的应用场景。

基本数据

JS基本数据 是存储在 栈内存(Stack Memory) 中, 它们的值是直接存储在变量访问的位置

那么什么是 栈内存 呢? 它是一种计算机内存中划分出来的一块 连续存储区域, 它的主要特点是 先进后出

当我们创建一个 基本数据 的变量时, 因为它占用空间小、大小固定, 所以会在 栈内存 中分配一个固定大小的空间来存储这个值, 当这个变量不再被使用时, 它所占用的空间会被自动释放, 因此 基本数据 的赋值和拷贝操作非常快速和高效

引用数据

JS引用数据 是存储在 堆内存(Heap Memory) 中的, 因为它们的大小是不确定的, 对象的属性和方法可能会动态增加或删除

那么什么是 堆内存 呢? 它是一种计算机内存中划分出来的一块 非连续存储区域, 它的特点是可以动态分配和释放内存空间, 但它需要手动管理内存空间

当我们创建一个 引用数据, 会在 堆内存 中分配一个内存空间用来存储对象的所有属性和方法, 然后在 栈内存 中创建一个指向该内存空间的 指针, 这个指针存储在变量访问的位置, 当这个变量不再被使用时,栈内存 中的指针被销毁, 但 堆内存 中的对象空间不会自动释放, 需要手动调用 垃圾回收机制 来释放这些空间

浅拷贝

浅拷贝是指将原对象或数组的值复制到一个新的对象或数组中,但是新的对象或数组的属性或元素依然是原对象或数组的引用,这意味着当我们修改其中一个对象或数组时,另一个对象或数组也会受到影响。因此,浅拷贝通常只针对引用类型。下面是常见的浅拷贝方法:拷贝是指将原对象或数组的值复制到一个新的对象或数组中,但是新的对象或数组的属性或元素依然是原对象或数组的引用,这意味着当我们修改其中一个对象或数组时,另一个对象或数组也会受到影响,以下方法都是浅拷贝(包括直接赋值)

  • Object.create(obj)
  • Object.assign({}, obj)
  • 若数组的结构、slice 等方法原数组包含对象,则这个对象也是浅拷贝

深拷贝

深拷贝是指将原对象或数组的值复制到一个新的对象或数组中,并且新的对象或数组的属性或元素完全独立于原对象或数组,即它们不共享引用地址。因此,当我们修改其中一个对象或数组时,另一个对象或数组不会受到任何影响。

js
let newObj = JSON.parse(JSON.stringify(obj)); // 最常用的深拷贝方法

但是,这种方法也存在一些缺陷:

  1. 不能处理 undefined、function 和 Symbol 等特殊数据类型,因为它们在 JSON 中没有对应的表示方式。
  2. 无法处理循环引用,即当一个对象的属性指向自身或形成循环引用关系时,深拷贝会陷入无限递归。

所以建议直接使用lodash 中的 cloneDeep 方法,手写的话可以看下下面这个代码

js
// map 用于记录出现过的对象, 解决循环引用
const deepClone = (target, map = new WeakMap()) => {
  // 1. 对于基本数据类型(string、number、boolean……), 直接返回
  if (typeof target !== "object" || target === null) {
    return target;
  }

  // 2. 函数 正则 日期 MAP Set: 执行对应构造题, 返回新的对象
  const constructor = target.constructor;
  if (/^(Function|RegExp|Date|Map|Set)$/i.test(constructor.name)) {
    return new constructor(target);
  }

  // 3. 解决 共同引用 循环引用等问题
  // 借用 `WeakMap` 来记录每次复制过的对象, 在递归过程中, 如果遇到已经复制过的对象, 则直接使用上次拷贝的对象, 不重新拷贝
  if (map.get(target)) {
    return map.get(target);
  }

  // 4. 创建新对象
  const cloneTarget = Array.isArray(target) ? [] : {};
  map.set(target, cloneTarget);

  // 5. 循环 + 递归处理
  Object.keys(target).forEach((key) => {
    cloneTarget[key] = deepClone(target[key], map);
  });

  // 6. 返回最终结果
  return cloneTarget;
};

事件循环,异步的宏任务和微任务

讲一下事件循环,还有其中的宏任务和微任务的区别

答:事件循环是由于 js 单线程的原因将任务分成同步任务和异步任务,然后同步任务的在主线程上运行,异步任务则由任务队列中的事件循环机制异步执行,异步任务完成后其回调函数就放到任务队列等待同步任务完成后执行。宏任务通常由定时器任务、I/O 任务、UI 渲染任务等操作触发,而微任务通常由 Promise、MutationObserver 等异步操作触发的回调函数组成。当主线程空闲时,会先执行当前任务队列中所有的微任务,然后再执行一个宏任务。如果宏任务队列中有多个任务等待执行,则按照先进先出的顺序执行。

详解

运行机制

JavaScript 引擎在执行 JavaScript 代码时,会将任务分为两类:同步任务异步任务。同步任务在主线程上执行,而异步任务则由任务队列中的事件循环机制异步执行。在异步任务完成后,就会将该任务对应的回调函数放入任务队列中,并等待主线程执行完当前所有的同步任务后再执行该回调函数。

事件循环

事件循环是指不断从任务队列中取出任务,并执行其对应的回调函数的过程。事件循环在 JavaScript 引擎内部以非常高效的方式运行,在等待异步 I/O 操作返回数据时,可以将 CPU 时间释放给其他线程使用。

事件循环的基本流程如下:

  1. 进入到 script 标签,就进入到了第一次事件循环.
  2. 遇到同步代码,立即执行
  3. 遇到宏任务,放入到宏任务队列里.
  4. 遇到微任务,放入到微任务队列里.
  5. 执行完所有同步代码
  6. 查看是否有微任务,如果有则将代码放入主线程,新写入主线程的代码执行完毕后,查看是否还有微任务,依次执行,直至清空。
  7. 更新 render(每一次事件循环,浏览器都可能会去更新渲染)
  8. 查看是否有宏任务,如果有则执行,将其代码放入主线程,重复步骤 2。

以此反复直到清空所有宏任务,这种不断重复的执行机制,就叫做事件循环

任务队列

任务队列是指在 JavaScript 引擎中用来存储异步任务的队列(其实就是一个执行栈),任务队列中的每个任务都与一个回调函数相关联。当异步任务执行完成后,JavaScript 引擎将相应的回调函数添加到任务队列中,等待主线程执行完当前的同步任务后再执行这些回调函数。

宏任务(Macro Task)

宏任务是指由 JavaScript 主线程执行的任务,它包括但不限于以下情况:

  • 浏览器事件(如 click、mouseover 等)
  • 定时器任务(如 setTimeout 和 setInterval)
  • 页面渲染(如 回流或重绘)
  • 事件回调(如 I/O、点击事件等)
  • 网络请求 (如 XMLHttpRequest 和 fetch 等)

微任务(Micro Task)

微任务是指由 JavaScript 引擎执行的任务,它在宏任务之后执行,但在下一次渲染之前执行。微任务通常是由宏任务中的某个特定任务触发的,并立即执行。常见的微任务有:

  • Promise 的回调函数
  • Async/Await 函数
  • MutationObserver 的回调函数
  • process.nextTick(Node.js 环境下)

简单的代码

js
console.log("Start");

setTimeout(() => {
  console.log("Timeout1");
}, 0);

Promise.resolve().then(() => {
  console.log("Promise 1");
}).then(() => {
  for(let i = 0 ; i < 9999; i++){
      console.log(`我是for循环的第${i}个`)
  }
});

setTimeout(() => {
  console.log("Timeout2");
},0);


console.log("End");

// 输出结果
Start
End
Promise1
我是for循环的第0个
我是for循环的第1个
.............
我是for循环的第9998个
Timeout1
Timeout2

Promise

问:讲一下 promise

答:Promise 是 ES6 的新特性,包含pending(进行中)、fulfilled(已成功)和 rejected(已失败)三种状态,是一种异步请求

详解

看链接:我终于真正理解 Promise 了! - 掘金 (juejin.cn)

vue 的生命周期函数

vue2

  • 【beforecreate】实例创建前。vue 完全创建之前,会自动执行这个函数。
  • 【Created】实例创建后。这也是个生命周期函数,beforecreate 执行完后会自动执行它。其中 data 的值是由在 created 中请求并初始化的。
  • 【beforemount】组件挂载前。模板即将挂载到页面的一瞬间会执行 beforemount
  • 【Mounted】组件挂载后挂载完成,也就是模板中的 html 渲染到了 html 页面中,此时一般可以做一些 ajax 操作。mounted 只会执行一次。
  • 【beforeupdate】视图更新前数据发生改变,还没有渲染之前 beforeupdate 会执行
  • 【updated】视图更新后当数据更新完重新渲染时,updated 这个函数会执行。
  • 【beforedestroy】失礼销毁前钩子函数在实例销毁之前调用。在这一步,实例仍然完全可用.
  • 【Destroy】实例销毁后做收尾工作,在结束之后做一些事情,比如定时器和全局更新要在这里销毁。

vue3

  • 1、beforeCreate -> 使用 setup()
  • 2、created -> 使用 setup()
  • 3、beforeMount -> onBeforeMount
  • 4、mounted -> onMounted
  • 5、beforeUpdate -> onBeforeUpdate
  • 6、updated -> onUpdated
  • 7、beforeDestroy -> onBeforeUnmount
  • 8、destroyed -> onUnmounted

ts 泛型

问:什么是 ts 泛型

答:泛型使用尖括号 <T> 来表示,并在定义函数、类或接口时指定类型参数。

any 和 unkonwn 的区别

问:any 和 unknown 的区别

答:any 类型,任何操作都是没有类型检查的,因此对其进行任意类型的赋值都是合法的。unknown 类型,只有进行类型检查或类型断言才能对变量进行操作,否则不能通过语法检查,同时 unknown 类型的值也不能赋值给 any 和 unknown 以外的类型变量

是否自己封装过组件

问:是否自己封装过组件

答:封装过一些全局可复用的组件,例如用户卡片,项目的卡片,头部导航栏等等。

es5 和 es6 区别

详解见:2.8 万字总结!!ES6 到 ES12 常用新特性! - 掘金 (juejin.cn)

  • let、const 关键字
  • 模板字符串
  • 函数扩展:默认值和箭头函数
  • 对象新增属性
  • 解构
  • class
  • proxy 和 reflect
  • symbol
  • set 和 map

es5 和 es6 实现对象继承

问:es5 和 es6 分别如何实现对象继承

答:ES5 利用 propertype 实现原型链继承,ES6 采用 class extends 实现继承,然后 super

详解见:ES5 和 ES6 分别是如何实现继承的? - 掘金 (juejin.cn)

同源策略

问:什么是同源策略,为啥要设置同源策略,get 和 post 什么时候跨域什么时候不跨域

答:同源策略是一种浏览器安全策略,它限制了一个网页中的文档或脚本如何与来自不同源(域、协议和端口)的资源进行交互。这是为了防止恶意网站通过脚本访问用户的敏感数据。通过 img、video 等 html 标签的请求是不跨域的,post 是都跨域

详解

什么是同源策略?

同源策略是一种浏览器安全策略,它限制了一个网页中的文档或脚本如何与来自不同源(域、协议和端口)的资源进行交互。这是为了防止恶意网站通过脚本访问用户的敏感数据。

在 Web 开发中,"源"由协议、域名和端口组成。同源策略要求两个页面只有在这三者完全相同的情况下才被视为同源。

解决跨域问题的方法

在面对同源策略的限制时,有一些方法可以解决跨域问题,使得不同源的资源能够进行安全的交互。

常用的解决跨域就是 jsonp 了,然后开发者模式下用 vite 的 proxy 就可以了(本质也是使用 nginx 代理),打包到服务器上用 nginx 代理。

服务端有没有跨域问题

服务端有没有跨域问题

答:有,通过 acess-origin 设置

路由知识 --- 怎么实现路由

怎么实现路由

答:不会。。。(当时确实不会)

详解

hashH5 history
监听window.onhashchangewindow.onpopstate
设置location.href = '#/user'history.pushState
需要支持需要后端支持(将所有的路由拦截返回 index.html)
  • hash 实现
    • hash 是 URL 中 hash (#) 及后面的那部分,常用作锚点在页面内进行导航,改变 URL 中的 hash 部分不会引起页面刷新
    • 通过 hashchange 事件监听 URL 的变化,改变 URL 的方式只有这几种:通过浏览器前进后退改变 URL、通过<a>标签改变 URL、通过 window.location 改变 URL,这几种情况改变 URL 都会触发 hashchange 事件
  • history 实现

    • history 提供了 pushState 和 replaceState 两个方法,这两个方法改变 URL 的 path 部分不会引起页面刷新
    • history 提供类似 hashchange 事件的 popstate 事件,但 popstate 事件有些不同:通过浏览器前进后退改变 URL 时会触发 popstate 事件,通过 pushState/replaceState 或<a>标签改变 URL 不会触发 popstate 事件。好在我们可以拦截 pushState/replaceState 的调用和<a>标签的点击事件来检测 URL 变化,所以监听 URL 变化可以实现,只是没有 hashchange 那么方便
  • nodejs 学习情况

  • 兴趣爱好

  • 有什么建议

参考文献

  1. 计算机网络|网络传输协议之:Http1、Http2、Http3📂📂 - 掘金 (juejin.cn)
  2. 面试官:详细说说 HTTP 常见的状态码有哪些? - 掘金 (juejin.cn)
  3. 超级详细的 js 数组方法详解 - 掘金 (juejin.cn)
  4. 前端工程化基石 -- AST(抽象语法树)以及 AST 的广泛应用 🔥 - 掘金 (juejin.cn)
  5. 前端人必须掌握的抓包技能 - 掘金 (juejin.cn)
  6. [万字总结] 一文吃透 Webpack 核心原理 - 掘金 (juejin.cn)
  7. vite 对比 webpack,有何提升? - 掘金 (juejin.cn)
  8. JavaScript | 闭包 - 掘金 (juejin.cn)
  9. 长话短说,带你了解 JavaScript 作用域,探索域的奥妙。 - 掘金 (juejin.cn)
  10. JavaScript 提升:掌握深拷贝与浅拷贝的技巧及如何手写深拷贝 - 掘金 (juejin.cn)
  11. 八股文: 讲讲什么是浅拷贝、深拷贝? - 掘金 (juejin.cn)
  12. 浅析 JavaScript 事件循环(Event Loop) - 掘金 (juejin.cn)
  13. 【超简单】一文解析宏任务与微任务:理解事件循环(Event Loop)机制 - 掘金 (juejin.cn)

备案号:闽ICP备2024028309号-1