前端高频面试知识点

121 阅读30分钟

浏览器原理篇

一、浏览器缓存机制

浏览器缓存机制是提升网页加载速度、减少服务器负载和网络带宽消耗的重要技术。

浏览器缓存行为
  • 浏览器每次发起请求,都会先在浏览器缓存中查找该请求结果及缓存标识
强缓存(Strong Cache)

特点:可以设置两种HTTP Header实现: ExpriesCache-control。不向服务器发送请求,直接使用本地缓存资源。

Expries(HTTP/1.0)

Expries的值是一个绝对时间(GMT格式),在此时间前会使用缓存,例如:Expires: Wed, 21 Oct 2025 07:28:00 GMT;缺点是Expries受限于本地时间,如果客户端和服务器时间不一致,会判断缓存错误。

Cache-Control (HTTP/1.1 优先级更高)

常用指令有:

  • max-age=3600: 资源的最大存活时间(单位:秒)资源在3600秒内有效,期间直接使用缓存
  • no-cache: 跳过强缓存,直接进入协商缓存
  • no-store:完全不使用任何缓存,每次都重新请求
  • public:表示该资源可以被任何节点缓存(包括客户端和代理服务器)
  • private:表示该资源只能被客户端缓存,代理服务器不能缓存

当Cache-Control和Expries同时存在时Cache-Control的优先级更高。

强缓存流程: 浏览器发起请求时,会先检查本地是否有该资源的缓存,并计算是否在Cache-Control的max-age或者Expries设定的有效期内。

如果有效: 直接读取磁盘/内存缓存,返回状态码200(from disk cache)或200(from memory cache)

如果失效: 进入下一阶段-协商缓存

协商缓存

如果首次缓存没有Cache-Control和Expries;或者Cache-Control的属性设置为no-cache时,又或者缓存过期,就需要发起请求验证资源是否有更新。向服务器发送请求时,服务器根据请求头的If-None-MatchIf-Modified-Since字段来判断是否命中协商缓存,如果命中,则返回304状态码并更新浏览器缓存有效期。

Last-Modified(响应头)和If-Modified-Since(请求头)(HTTP/1.0)
  1. 服务器返回资源最后修改时间:Last-Modified:Wed, 21 Oct 2024 07:28:00 GMT
  2. 发送请求时会将当前的Last-Modified值作为If-Modified-Since字段内容,放在请求头中发送给服务器
  3. 服务器判断比较:对比资源的当前最后修改时间和If-Modified-Since值
  4. 如果时间一致,说明资源未修改,返回304 Not Modified,不返回内容,使用本地缓存;
  5. 如果时间不一致,说明资源已修改,返回200,和新的资源。

缺点: 精度到秒,如果文件在1秒内改变,无法识别;文件内容可能只是被 touch 了一下,修改时间变了,但内容没变,这时也会导致缓存失效。

Etag(响应头)和If-None-Match(请求头)(HTTP/1.1 精准度优先级更高)
  1. Etag类似于文件指纹,是资源唯一标识(如哈希值)
  2. 服务器返回Etag:"abc123"
  3. 发送请求时会将当前的Etag值作为If-None-Match字段内容,放在请求头中发送给服务器
  4. 服务器判断比较:对比资源当前的Etag值和If-None-Match值
  5. 如果内容一致,说明资源未修改,返回304 Not Modified,不返回内容,使用本地缓存;
  6. 如果内容不一致,说明资源已修改,返回200,和新的资源。
缓存流程
  1. 用户输入URL,浏览器发起请求
  2. 浏览器检查本地是否有缓存
  3. 无缓存,直接向浏览器请求,流程结束
  4. 有缓存判断是否存在强缓存(Expries和Cache-Control
  5. 判断强缓存是否过期,未过期:使用本地缓存(disk cache/memory cache),已过期:进入协商缓存
  6. 发起请求,请求头携带If-Modified-SinceIf-None-Match与服务器进行对比
  7. 服务器判断: 资源未变,返回304、浏览器继续使用缓存;资源改变,返回200和新内容,更新缓存。
缓存位置
  • Memory Cache: 内存中的缓存,速度快,关闭标签后清除
  • Disk Cache: 硬盘中的缓存,持久化,容量大
  • Service Worker Cache: 运行在浏览器背后的独立线程,无法直接访问DOM,可用作离线缓存(PWA)、消息推送和网络代理,传输协议必须是HTTPS
  • Push Cache: (推送缓存)短暂存在于连接中的缓存
强制刷新与缓存行为
  • 普通刷新(F5刷新/点刷新按钮): 不使用强缓存,但可能使用协商缓存;如果匹配的话memory cahce可用的会被优先使用,其次是disk cache。
  • 强制刷新(Ctrl+F5/Shift+Refresh): 浏览器不使用缓存,强制从服务器中获取
  • 清空缓存并硬刷新: 彻底清除本地缓存

二、常见的HTTP请求头和响应头

请求头由客户端发送给服务器,包含了关于客户端环境、期望的响应格式等信息。
请求头作用说明
Accept告诉服务器客户端能接收的内容类型,如:text/html,application/json
Accept-Encoding客户端支持的压缩格式如gzip,有效减少传输体积
Accept-Language客户端偏好的语言,如zh-CN,en-US, 用于国际化内容返回
Content-Type表示请求体的数据格式如:application/jsonmultipart/form-data
Authorization携带身份认证信息,如Bearer 〈token〉
Referer标明当前请求是从哪个页面跳转来的,用于防盗链或日志分析
Origin表示跨域请求的源(协议+域名+端口),用于CORS判断是否允许跨域
Cookie自动携带与当前域名匹配的Cookie,用户维持会话状态:登录态
HostHTTP/1.1规范中唯一一个必须的请求头,指定请求的服务器的域名和端口号
Cache-Control控制静态资源缓存策略
If-Modified-Since/If-None-Match用于协商缓存,判断资源是否更新
响应头
响应头作用说明
Cache-Control指定缓存策略的核心指令。例如 max-age=3600 表示资源可缓存1小时,no-cache 表示使用前必须向服务器验证,no-store 表示禁止缓存。
Expires指定资源的绝对过期时间(GMT格式)。如果 Cache-Control 中的 max-age 存在,则优先使用 max-age
Accept-Language客户端偏好的语言,如zh-CN,en-US, 用于国际化内容返回
Etag资源的唯一标识符(如哈希值)。浏览器下次请求时通过 If-None-Match 发送此值,服务器可据此判断资源是否变更,决定返回 200 OK 还是 304 Not Modified
last-Modified资源最后一次被修改的日期和时间(GMT格式)。浏览器下次请求时通过 If-Modified-Since 发送此值进行验证。
Content-Type指明响应体的MIME类型和字符编码。例如 text/html; charset=utf-8application/jsonimage/png
set-Cookie向浏览器设置一个Cookie。可包含 Secure(仅HTTPS传输)、HttpOnly(禁止JavaScript访问)、SameSite(限制跨站请求)等安全属性。
Date响应消息生成的日期和时间(GMT格式)
Connection控制网络连接。keep-alive 表示保持TCP连接以便复用;close 表示发送完响应后关闭连接。
Access-Control-Allow-OriginCORS(跨域资源共享)头部,指定哪些外部域可以访问该资源。例如 https://example.com 或 *(通配符,允许所有,但有安全风险)。

三、HTTP和HTTPS

HTTP

HTTP是超文本传输协议,基于TCP/IP通信协议传递数据,无状态,不连接,使用明文传输。

HTTPS

HTTPS是基于SSL/TLS协议对数据进行加密传输的网络协议,比HTTP安全。

特性HTTPHTTPS
安全性不安全,数据以明文形式传输,容易被窃取和篡改安全,通过SSL/TLS协议对数据进行加密传输
加密机制无加密使用SSL/TLS证书进行非对称加密(握手阶段)和对称加密(数据传输阶段)
默认端口80443
所需证书不需要需要由受信任的证书颁发机构(CA)签发的SSL/TLS证书
URL前缀http://https://
OSI网络模型应用层传输层

四、说说HTTP1.0、HTTP1.1、HTTP2.0、HTTP3.0的区别

HTTP1.0

HTTP协议最早的版本,明显存在性能问题。

核心特点:

  • 无状态、短连接:每次请求都需要跟服务器建立一个TCP连接,服务器完成请求后会立即断开。如果一个页面需要加载10个资源(HTML、CSS、JS、图片),浏览器就需要建立10次独立的TCP连接。
  • 高延迟:每次建立TCP连接都需要“三次握手”,关闭连接需要“四次挥手”,这个过程消耗大量时间,导致页面加载速度变慢。
  • 功能简单:只能支持基本的请求方法(GET、POST、HEAD)。
  • 支持持久连接的雏形:通过非标准的Connection:keep-alive头,提示服务器不要立即关闭连接,但这并不是默认行为。
HTTP1.1

HTTP1.1是HTTP1.0的标准化和重大改进,它解决了1.0的许多核心性能问题。 核心特点:

  • 持久连接:默认情况连接在完成之后不会被立即关闭,而是保持打开状态,在一个TCP连接上可以传送多个HTTP的请求和响应。
  • 管道运输:允许客户端在同一个连接上连续发送多个请求,这存在队头阻塞问题,如果第一个请求的响应很慢,它会阻塞后面所有请求的响应。管道化在实际中被默认禁用。
  • 冗余头部:每个请求都携带大量重复的Header(Cookie、User-agent等),导致带宽浪费。
  • 缓存控制:引入了更精细的缓存控制机制,如Cache-Control头、Etag等。
  • 更多请求方法:PUT、DELECT、OPTIONS等方法。
HTTP2.0

核心特点:

  • 二进制分帧:http1.0/1.1使用纯文本格式,2.0采用二进制格式传输数据,将请求和响应分解成更小的二进制帧,如HEADERS帧、DATA帧。
  • 多路复用:基于二进制分帧,一个TCP连接上可以并行发送和接收多个请求和响应,且它们互不干扰,彻底解决了HTTP1.1队头阻塞的问题。
  • 头部压缩:使用HPACK算法对HTTP(请求和响应)头部进行压缩,显著减少了头部信息的大小,降低传输开销。
  • 服务器推送:允许服务器主动推送它认为客户端需要的资源,而无需客户端请求。 新的挑战: TCP层的队头阻塞:HTTP2.0虽然解决了应用层的队头阻塞,但底层仍依赖TCP,TCP是按顺序传输字节流的协议,如果一个TCP包丢失,所有后续的包都必须等待这个丢失的包重传成功,这被称为“TCP层的队头阻塞
HTTP3.0

为了解决http2.0遗留下的TCP层的队头阻塞问题。 核心改革:

  • 基于UDP协议:弃用TCP、拥抱QUIC,QUIC运行在UDP之上,UDP本身是无连接、不保证可靠性的协议。
  • 彻底解决TCP层队头阻塞:QUIC在协议层原生支持多路复用,每个数据流(Stream)都是独立的,如果一个流发生包丢失,只会阻塞该流本身的数据重组,其他流可以继续传输。
  • 更快的连接建立: TCP+TLS问题:建立一个安全的HTTP/2连接需要一次TCP三次握手(1RTT)和一次TLS握手(1-2RTT),总共需要2-3个RTT延迟。 QUIC解决方案:对于新客户端,QUIC可以在1个RTT内完成连接和加密,对于曾经连接过的客户端,QUIC支持0RTT快速重连,客户端可以在第一个数据包中就发送应用数据,极大减少了延迟。
  • 连接迁移:当用户的网络发生变化时,TCP连接会中断,需要重新建立。QUIC使用唯一的连接ID来标识连接,而不依赖IP地址和端口,因此,当设备IP改变时,只要连接ID不变,QUIC连接可以无缝在新的网络上继续。
  • 内置加密:QUIC从设计之初就强制要求加密,所有的头部和数据都是加密的,安全性更高。

五、地址栏输入URL敲下回车后发生了什么?

1. URL解析:

浏览器判断输入的内容是URL还是关键词,如果是关键词交给默认搜索引擎,如果是URL,会对完整的URL进行解析,分解出协议、主机(域名)、端口、路径、参数等。

2. DNS解析:
  • 计算机在网络中通过IP地址进行通信,需要将www.google.com转换成对应的IP地址

    判断是正确的URL格式之后,DNS会在缓存中查询是否有当前域名的IP地址。 基本步骤:浏览器缓存(浏览器检查是否在缓存中)---〉操作系统缓存(操作系统DNS缓存,去本地的hosts文件查找)---〉路由器缓存(路由器DNS缓存)---〉ISP DNS缓存(在客户端设置的首选DNS服务器,又称本地的DNS服务器)

    递归查询:如果ISP的DNS服务器也不知道,它会向根域名服务器、顶级域名服务器(.com)、权威域名服务器发起递归查询,最终找到负责google.com的服务器并获取IP地址。

3. 建立TCP连接:

浏览器使用获得的IP地址和端口号,通过操作系统协议发起TCP连接。通常使用的是HTTPS协议,在TCP连接的基础上还需要建立安全的TLS连接。

三次握手

  • SYN: 客户端发送一个SYN(同步)包到服务器,表示“想要和你建立连接”,并附带了一个随机的序列号(Seq=X)。
  • SYN-ACK: 服务器收到后,回复一个SYN-ACK(同步-确认)包,表示“我收到了你的请求,我也想和你建立连接”,。包含自己的序号(Seq=Y)和对客户端序列号的确认(ACK=X+1)
  • ACK: 客户端收到后,再回复一个ACK(确认)包,确定收到了服务器的SYN,包含确认号(ACK=Y+1),连接正式成立。

TLS握手(仅对HTTPS)

  • 第一次握手:客户端发送请求Client Hello

    告知支持的协议版本和加密方法,还有客户端生成的随机数(client random),用于生成对话密钥。

  • 第二次握手:服务器回复Server Hello

    服务器确认使用的加密通信协议版本,确认使用的加密方法,发送SSL证书、公钥以及一个随机数(Server random)

    客户端验证证书的合法性(颁发机构、有效期、域名是否匹配等),如果这三个任何一个环节发现问题,浏览器会向访问者显示一个警告,由其选择是否还要继续通信。如果证书受信任,或用户接受了不受信任的证书,浏览器会生成一串新的随机数(Premaster secret)

  • 第三次握手:客户端回应

    此前,浏览器根据前三次握手中的随机数Client random、Server random、Premaster secret通过一定的算法来生成“会话密钥(Session Key)”,这个会话密钥就是接下来双方进行对称加密解密使用的密钥。

  • 第四次握手:服务端回应

    服务端收到客户端的回复,利用已知的加密解密方式进行解密,服务器收到客户端的第三个随机数(Premaster secret)之后,使用同样的算法计算出“会话密钥”(Session Key)。 至此,整个握手阶段全部结束。接下来,客户端与服务器进入加密通信,就是完全使用普通的HTTP协议,只不过用“会话密钥”加密内容。

4. 发送HTTP请求:

TCP连接建立之后,浏览器会通过这个连接向服务器发送一个HTTP请求报文。 请求报文由请求行(GET/HTTP/1.1)、请求头Headers(Host/User-Agent/Accept/Connection:keep-alive等)、请求体(Body)等。

5.服务器处理请求并返回响应:

服务器接收到请求后,会根据请求的路径或方法进行处理,处理过程包括:读取静态文件(HTTML、CSS、JS、图片等)、查询数据库、执行应用程序逻辑等。

处理完成后,服务器会生成一个HTTP响应报文并发送回浏览器。

一个典型的响应报文包括:状态行(HTTP/1.1 200 OK)、响应头(Content-type:text/html; charset=UTF-8;Set-cookie;Cache-control:max-age:3600)、响应体(通常是HTML代码)。

6.浏览器解析和渲染页面:

浏览器将收到的HTML、CSS、JAvaScript等资源转换成用户可见的网页。

  • 解析HTML,构建DOM树 DOM Tree 浏览器会从上到下解析HTML文档生成DOM树;
  • 解析CSS,构建CSSOM树 CSSOM Tree 浏览器解析遇到样式时,会进行异步下载,下载完成后构建CSSOM树;它包含所有元素的样式信息。
  • 构建渲染树Render Tree 浏览器将DOM树和CSSOM树合并,生成一个渲染树;渲染树只包含需要在页面上显示可见的元素以及计算后的样式(display:none)的元素不会在渲染树中。 link不会阻塞DOM树构建过程,但会阻塞渲染树构建过程。
  • 布局Layout 根据渲染树将DOM节点每一个节点布局在屏幕的正确位置上。
  • 绘制Paint 浏览器将渲染树的每个像素绘制到屏幕上,填充颜色、边框、阴影、图片等视觉内容。
  • 合成与显示 现代浏览器会将页面分成多个图层,分别绘制,各层绘制结果合并成一幅完整的画像,最终显示在用户的屏幕上。
7.断开连接(四次挥手):

数据传输完成后,为了释放资源,TCP连接需要被关闭。 这个过程被称为“四次挥手”:

  • 客户端发送FIN包,表示“我这边数据发完了,准备关闭”。
  • 服务器回复ACK包,表示“我收到了你的关闭请求”。
  • 服务器发送FIN包,表示“我这边也发完了,准备关闭”。
  • 客户端回复ACK包,连接正式关闭。

六、回流和重绘

回流(重排Reflow)

第一次确定节点的大小和位置,称为布局(layout),之后对节点的大小和位置修改重新计算称之为回流。

什么情况下会引起回流?
  1. DOM结构发生改变(添加新的节点或者移除节点)
  2. 改变了布局(修改了width、height、padding、font-size等值)
  3. 浏览器窗口大小改变(resize)
  4. 计算某些布局相关的属性( offsetTopoffsetHeightclientWidthscrollWidth等)
  5. 调用getComputedStyle方法获取尺寸、位置信息
  6. 激活CSS伪类(:hover)
重绘(RePaint)

当渲染树中的一些元素需要更新,而这些属性只是影响元素的外观、风格而不影响布局的,这个过程称为重绘。

什么情况下会引起重绘?

修改背景色、文字颜色、边框颜色、样式等。

回流一定会引起重绘,因为回流会重新计算布局,然后重新绘制。

如何在开发中避免不必要的回流和重绘?
  1. 使用class集中改变样式,避免逐个修改样式,最好一次性更改className或cssText。
  2. 脱离文档流进行修改
  3. 尽量避免通过getComputedStyle获取尺寸、位置等信息
  4. 避免频繁读取布局信息
  5. 使用 transformopacity 实现动画,这两个属性可以在合成 (Compositing)  阶段由GPU处理,通常只会触发合成,而不会触发重绘回流,性能最佳。

七、浏览器资源解析

Script元素

当浏览器在解析HTML时,遇到script元素,停止继续构建DOM树,下载script代码并执行后,才会继续解析HTML,构建DOM树。

async属性的Script(异步执行,仅对外部脚本有效)

浏览器解析到带async属性的script时,不会中断HTML解析,而是并行下载脚本,当脚本下载完成后,中断解析并执行脚本;一旦加载完立即执行

如果有多个js脚本,async标记的脚本哪个先下载结束,就先执行哪个脚本。

defer属性的Script(延迟执行,仅对外部脚本有效)

浏览器解析到带defer属性的script时,不会中断HTML解析,而是并行下载脚本,当浏览器解析完HTML时、在DomContentloaded事件之前执行下载完成的脚本

如果有多个js脚本,defer标记的脚本按js脚本书写顺序执行。

同时使用async和defer属性,defer不起作用,浏览器行为由async属性决定。
浏览器解析不同资源时行为
  • 遇到CSS样式资源CSS会异步下载,不会阻塞浏览器构建DOM树,但是会阻塞渲染,在构建渲染树时,会等CSS下载解析完毕后才进行;
  • 遇到JS脚本资源时,需要等待JS脚本资源下载并执行后才会继续解析HTML;如果JS脚本加上async会异步执行,defer会是延迟执行;
  • CSS加载阻塞后面的JS语句的执行,因为HTML中有一项规定,浏览器在执行Script脚本前,必须保证外联的CSS样式解析完成,因为JS会去获取或者变更DOM的CSS样式,如果外联的CSS没解析好,获取到的结果是不准确的。
  • 遇到Img图片时,直接异步下载不会阻塞解析;下载完毕后用图片替换原有的src的地方。
Load和DOMContentLoaded区别
  • Load事件触发代表页面中的DOMCSSJS,图片已经全部加载完毕。
  • DOMContentLoaded事件触发代表初始的HTML被完全加载和解析,无需等待CSS带async的JS图片加载。

八、浏览器安全

XSS

XSS(跨站脚本攻击)是指攻击者在返回的HTML中嵌入javascript脚本,从而拿到用户的信息(cookie)并进行操作。xss分为存储型、反射型和文档型。

存储型

攻击者将恶意脚本存储到网站的数据库中,当其他用户正常访问包含这些内容的页面时,恶意脚本就会从服务器加载并执行。

常见场景:一个博客的评论系统,攻击者提交脚本代码,服务器未过滤,将这条评论存入了数据库,之后所有访问这篇博客的用户,在加载评论时都会执行这个恶意脚本。 特点:危害最大,是持久性的,所有访问该页面的用户都会收到影响。

反射型

将恶意脚本作为请求URL的参数,经过服务器解析响应,拼接在HTML中传回客户端,然后浏览器解析执行恶意脚本。 特点:是一次性的,仅对惦记链接的用户生效,恶意脚本不存储在服务器上。 服务器不会存储这些恶意脚本。

文档型

攻击的Payload在客户端(浏览器)修改DOM环境时发生,不经过服务器。恶意代码的注入和执行都是在浏览器通过javascript操作DOM完成的。 特点: 整个过程在客户端完成,服务器响应本身可能是安全的。

XSS的防御
  1. 对输入进行转义和过滤:对引号、尖括号、斜杠进行转义,让代码在html解析的过程中无法执行;过滤就是把script标签删除。
  2. CSP:内容安全策略,通过HTTP头告诉浏览器只允许加载指定来源的脚本、样式等资源,从根本上杜绝内联脚本和未经允许的外部脚本的执行。
  3. 设置HttpOnly:cookie设置httpOnly后,会禁止javascript脚本访问cookie,有效缓解cookie被盗。
CSRF

CSRF(跨站请求伪造)就是黑客诱导用户跳转恶意网站,然后利用用户的登录态发起恶意请求;原理就是http请求会自动携带cookie,而且是HTTP目标请求域名的cookie。

特点
  • 攻击者并不能拿到用户的cookie,只是冒用。
  • 整个过程用户可能完全不知情。
  • 核心前提是用户必须已经登录目标网站。
CSRF的防御
  1. Anti-CSRF Token:服务器会在用户会话中生成一个随机的、不可预测的Token。这个令牌必须包含在每一个状态改变的请求中,服务器收到请求后,必须验证这个令牌是否有效且匹配当前会话。因为攻击者无法从目标网站中获取这个动态令牌,所以伪造的请求会因缺少或错误的令牌而被拒绝。
  2. 检查SameSite Cookies:该属性表示Cookie不随着跨域请求发送,可以很大程度减少CSRF的攻击;SameSite=Strict:完全禁止第三方cookie,SameSite=Lax:宽松模式,在大多数跨站子请求(图片加载、iframe)中不发送cookie,但在顶级导航(如从外部链接点进来)时会发送。
  3. 验证Referer/Origin头:服务器检查请求头中的Referer或Origin字段,判断是否来自合法的源(自己的网站域名),但这种方法不可靠,Referer头会被伪造。

九、跨域

同源策略
  • 只有协议域名端口都相同的叫同源策略。只要有一个不同,就是跨域
哪些行为受同源策略的限制
  • CookieLoacalStorageIndexDB无法读取。(访问存储在浏览器中的数据,如localStorage和IndexDB市以源进行分割。每个源都拥有自己单独的存储空间)
  • DOM无法获得。
  • AJAX请求不能发送。
跨域解决方法
1.CORS(Cross-Origin-resource-Sharing)跨域资源共享

它是一种服务器端的授权机制,服务器通过在HTTP响应头中添加特定的字段,来告诉浏览器:我允许X源的请求访问我的资源。由后端设置响应头Access-Control-Allow-Origin)来授权跨域访问。

CORS请求步骤:

当我们发起跨域请求时,如果是复杂请求,浏览器会帮我们自动触发预检请求(OPTIONS),用于确认目标资源是否支持跨域。如果是简单请求,则不会触发预检,直接发出正常请求

浏览器会根据服务端响应的headerAccess-Control-Allow-Origin)进行判断,如果响应支持跨域,则继续发出正常请求,如果不支持,则在控制台显示错误。

2.JSONP(JSON with Padding)

利用script标签没有跨域限制的特性。通过script标签指向一个需要访问的地址并提供一个回调函数来接收数据。

JSONP的缺点:

只支持GET请求;存在XSS风险;错误处理困难。

3.postMessage

window.postMessage()可以安全的实现跨域通信,从一个窗口向另一个窗口发送消息。这个方法接收两个参数:你想发送的数据(Message)和目标窗口的源(taegetOrigin)。

4.代理服务器(proxy)

在开发环境或生产环境中使用;原理:在客户端和目标服务器之间架设一个“中间人”。

开发环境: Webpack/Vite等构建工具内置的开发服务器可以配置代理。 前端请求http://localhost:3000/api/users, 开发服务器将其代理转发到http://localhost:5000/api/users, 对浏览器来说是同源请求。

生产环境:使用Nginx反向代理服务器。将/api/* 的请求代理到后端服务器。

十、Cookie、 localStorage、sessionStorage

Cookie

一开始设计的时候并不是用来做数据存储的,而是为了让HTTP具有状态;比如登录的标识存在cookie里,请求时就会自动携带cookie,这让无状态的HTTP请求变得能够标识请求的状态(身份)

Cookie和session的区别

两个都可以用来存私密的东西,比如用户身份,但是cookie数据保存在客户端session数据保存在服务器端

Cookie有效期

可通过ExpiresMax-Age两个属性来设置。Expires是过期时间;Max-Age用的是一段时间间隔,单位是秒,从浏览器收到报文开始计算。 过期时间如果设置为负数0,则浏览器关闭直接被销毁。

Cookie、 localStorage、sessionStorage区别
特性cookielocalStoragesessionStorage
存储大小约4KB约5-10MB约5-10MB
生命周期可设置过期时间Expries/Max-Age永久存储,除非手动删除页面会话期间有效
与服务器通信每次都会在请求头中携带不参与服务器通信不参与服务器通信
访问权限所有同源窗口共享所有同源窗口共享仅当前标签页共享
是否随请求发送是,每次同域HTTP请求都会自动携带否,需要JS获取否,需要JS获取
API通过document.cookiesetItem(key,value),getItem(key),removeItem(key),clear()同localStorage
存储位置浏览器和服务器浏览器浏览器
主要用途识别用户、会话管理(保持用户登录状态)、跟踪用户行为、个性化设置存储大量持久化数据(如用户偏好、离线数据)存储临时会话数据(如表单、草稿等)

十一、进程和线程

进程(Process)

进程是操作系统进行资源分配和调度的基本单位;可以把它看成一个正在运行的程序实例

核心特点

1. 独立的内存空间: 每个进程都有自己独立的虚拟地址空间(包括代码段、数据段、堆、栈等。)一个进程不能直接访问另一个进程的内存数据。

2. 资源拥有者: 进程拥有系统资源,如打开的文件、网络连接、CPU时间、内存等。

3. 开销大: 创建、销毁和切换进程需要消耗较多的系统资源(CPU和内存)。

线程(Thread)

线程是程序执行的最小单位,也称是CPU调度的基本单位;线程被包含在进程之中,一个进程中可以并发多个线程,每个线程并执行不同的任务。

核心特点

1. 共享进程资源: 同一个进程内的所有线程共享该进程内容空间系统资源

2. 独立的执行流: 每个线程有自己的程序计数器、寄存器集合和栈,保证了每个线程可以独立的执行代 码。

3. 开销小: 创建、销毁和切换线程的开销远小于进程,因为它们共享内存空间。

为什么需要线程?

1. 提高响应性: 在图形界面程序中,一个线程处理UI,另一个线程处理耗时操作。

2. 资源共享: 线程共享内存,通信更加高效。

3. 经济性: 创造线程比创造进程的开销小。

打个比方:进程像独立的工厂,线程像工厂里的流水线。工厂之间相互独立,但工厂内的流水线共享资源、协同合作。

JS 为什么是单线程的
  • JavaScript作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
浏览器是多进程的优点
  • 默认新开 一个 tab 页面新建一个进程,所以单个 tab 页面崩溃不会影响到整个浏览器。
  • 第三方插件崩溃也不会影响到整个浏览器。
  • 多进程可以充分利用现代 CPU 多核的优势。

十二、浏览器事件循环机制

JavaScript是单线程的,但是单线程会遇到“阻塞”问题(比如网络请求、定时器卡住主线程),事件循环就是为了解决单线程异步问题,让js能够非阻塞地处理任务。

事件循环(Event Loop)执行顺序
  1. 执行调用栈中的所有同步代码;
  2. 检查微任务队列,执行所有的微任务(按顺序执行);
  3. 微任务执行完毕后,浏览器判断是否需要渲染(比如DOM的变化);
  4. 从宏任务队列中取下一个宏任务执行,重复上述步骤(循环)。
异步代码执行顺序规则
  1. 同步代码优先执行
  2. 微任务在宏任务之前执行
  3. 同类型任务按添加顺序执行
  4. 每个宏任务执行完后都会清空微任务队列
任务队列和微任务队列

任务队列:存放宏任务,如setTimeout/setIntervalsetImmediate(IE/Node)、I/O操作

微任务队列: 存放微任务,promise.then/catch/finallyasync/await(本质是Promise)、 process.nextTick(Node.js)、MutationObserver(监听DOM变化)。

注: new Promise构造函数本身同步执行的,then()/catch()/finally()方法是异步的。

事件循环题目测试

console.log('start');

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

async function async2() {
  console.log('async2');
  return Promise.resolve().then(() => {
    console.log('async2 then');
  });
}

setTimeout(() => {
  console.log('setTimeout1');
  new Promise((resolve)=> {
   resolve();
  }).then(()=>{
    console.log("timeout1 promise");
   setTimeout(()=>{
     console.log("setTimeout2");
  },0);
  });
}, 0);

async1();

Promise.resolve()
  .then(()=>{
    console.log("promise1");
    return new Promise((resolve)=>{
      setTimeout(()=>{
      resolve("timeout2 promise");
      console.log("timeout3 promise");
      },0);
    });
  })
  .then((res)=>{
    console.log(res);
  });

console.log('end');

输出结果为

start
async1 start
async2
end
async2 then
async1 end
promise1
setTimeout1
timeout1 promise
timeout3 promise
timeout2 promise
setTimeout2

这段代码我们按照事件循环的同步代码→微任务→宏任务规则,结合async/await的底层逻辑、Promise 状态变化,一步步拆解执行顺序:

执行步骤:
1. 全局同步代码执行
  • console.log('start') → 输出:start

  • 定义async1/async2函数(仅声明,不执行)

  • setTimeout(...)(第一个定时器):回调作为宏任务 1加入宏队列

  • 调用async1()

    • console.log('async1 start') → 输出:async1 start

    • 执行await async2()

      • 调用async2() → console.log('async2') → 输出:async2async2返回Promise.resolve().then(...),此then回调(console.log('async2 then'))加入微任务 1
      • await暂停async1,需等待async2返回的 Promise 完成后,async1后续代码(console.log('async1 end'))才会入微队列
  • Promise.resolve().then(...):此then回调(内部逻辑)加入微任务 2

  • console.log('end') → 输出:end

2. 同步代码执行完毕,处理微队列(按入队顺序)
  • 微任务 1console.log('async2 then') → 输出:async2 then此时async2的 Promise 完成,async1await后的代码console.log('async1 end')加入微任务 3

  • 微任务 2:执行回调逻辑:

    • console.log("promise1") → 输出:promise1
    • 返回new Promise((resolve)=>{setTimeout(...)}):内部setTimeout回调作为宏任务 2加入宏队列;此 Promise 为 pending,需等待setTimeoutresolve触发后才会 fulfilled,因此后续的.then((res)=>{console.log(res);})暂不加入微队列
  • 微任务 3console.log('async1 end') → 输出:async1 end

3. 微队列清空,处理宏队列 1(第一个 setTimeout 回调)
  • console.log('setTimeout1') → 输出:setTimeout1
  • new Promise((resolve)=>{resolve();}).then(...):此then回调加入微任务 4
4. 处理微队列 4
  • console.log("timeout1 promise") → 输出:timeout1 promise
  • 内部setTimeout(...)(第二个定时器):回调作为宏任务 3加入宏队列
5. 微队列清空,处理宏队列 2(Promise 内的 setTimeout 回调)
  • resolve("timeout2 promise"):使外层 Promise 变为 fulfilled,后续的.then((res)=>{console.log(res);})加入微任务 5
  • console.log("timeout3 promise") → 输出:timeout3 promise
6. 处理微队列 5
  • console.log(res) → 输出:timeout2 promise
7. 微队列清空,处理宏队列 3(第二个定时器回调)
  • console.log("setTimeout2") → 输出:setTimeout2