day-092-ninety-two-20230614-HTTP网络-辅助知识-前端性能优化
HTTP网络
从输入URL地址到看到页面的步骤
-
从输入URL地址到看到页面,中间都经历了啥
-
第一步:URL地址解析。
http://www.xxx.com:80/index.html?lx=1&from=weixin#video
- URI/URL/URN
- URI: 统一资源标识符。
- URL与URN的统称。
- 平时我们看到的URI,其实集散控制系统的就是URL。
- URL与URN的统称。
- URL:统一资源定位符。
- 网址。
- URN:统一资源名称。
- 如图书编号。
- URI: 统一资源标识符。
- 解析信息:
- 传输协议:
- 作用:负责客户端和服务器端之间信息的传输(可以理解为快递小哥)。
- 分类:
http
:即HyperText Transfer Protocol
,超文本传输协议。- 除传输文本内容外,还可传输图片和音视频等。
https
:即Hypertext Transfer Protocol Secure
,HTTP
+SSL
,更安全的传输协议,经过加密处理。ftp
:即File Transfer Protocol
,文件传输协议,主要用于往服务器上上传内容
和下载内容
。
- 域名:
- 端口号
- 作用:区分相同服务器上部署的不同项目的,取值范围
0~65535
之间。- 浏览器有默认端口号机制:我们在地址栏中输入URL地址,如果没有写端口号,则浏览器会根据当前的传输协议,自动把端口号加上!
- http -> 80
- https -> 443
- ftp -> 21
- 作用:区分相同服务器上部署的不同项目的,取值范围
- 请求资源的路径名称。
- 问号传参信息
- 哈希值
- Hash值。
- 传输协议:
-
第二步:缓存检查
- 通过一个url网址,首先得到的是一个html。渲染过程中,遇到link标签向服务器发送拿到css代码,遇到script标签向服务器发送拿到js代码,遇到img标签向服务器发送拿到图片文件。
- 如果浏览器上有对应的路径文件的缓存,就直接拿,而不发送请求到服务器去拿。
- 针对静态资源文件:强缓存和协商缓存。
- 例如:html、css、js、图片、音频、视频…
- 缓存存储的位置:
- 虚拟内存(Memory Cache):存储全局变量、
- 如内存条、显卡内存、CPU内存。
- 物理内存(Disk Cache):
- 硬盘:固态硬盘与机械硬盘。
- 一般情况下,物理内存与虚拟内存都存一份。
- 虚拟内存(Memory Cache):存储全局变量、
- 浏览器读取缓存的思路:
- 正常刷新页面:先从虚拟内存中获取,如果不存在,再去物理内存中查找。
- 关闭页面重新打开:直接去物理内存中查找。
- 强制刷新页面(ctrl+F5):直接去服务器获取最新的。
- 读取缓存的速度:
- 虚拟内存中读取:速度一般在0ms。
- 物理内存中读取:速度较快,一般在10ms以外。
- 服务器中读取:速度最慢,在20ms-1000ms左右。
- 不论是强缓存还是协商缓存,都是由服务器进行设置,浏览器自动配合完成相应的缓存机制!
- 强缓存:
- 第一次访问网站,本地没有任何的缓存,需要向服务器发送请求;
- 服务器在返回相应资源信息的时候,如果想开启强缓存机制。
- 会在响应头中设置相关的字段:
Expires
: 存储缓存过期的具体时间。- 这个是
http/1.0
版本的协议中需要设置的字段。
- 这个是
Cache-Control
: 存储过多久缓存将过期(单位:秒)或者其它信息。- 这个是
http/1.1
版本的协议中需要设置的字段。
- 这个是
- 后台一般这两个字段都返回。至少要设置一个,根据那个版本来做。如果两个都返回,浏览器两个都支持会以最新的
Cache-Control
来设置。
- 会在响应头中设置相关的字段:
- 服务器在返回相应资源信息的时候,如果想开启强缓存机制。
- 当浏览器获取到服务器返回的资源信息,除了正常的渲染以外,还会去看响应头中是否有强缓存标识,即
Expires
与Cache-Control
字段。- 如果没有则啥都不处理。
- 如果有,则把本次获取的资源信息和这两个响应头信息,缓存在客户端本地!在虚拟内存和物理内存中都存储一份。
- 第二次及以后访问这个网站,首先看本地是否有
具备在效期的缓存信息
。- 没有具备/缓存过期了:重新向服务器发送请求,来获取最新的资源。
- 有:则直接渲染缓存中的信息,无需再向服务器发送请求了。
- 强缓存的几个特点:
- 可以不和服务器通信,直接就能拿到资源文件了。
- 导致可能和服务器失联,用的都是第一次缓存的文件。
- 想拿到服务器上最新的资源文件:需要html不做强缓存,及用webpack打包文件时,给文件名加上根据内容所生成的hash值。
- 不论从服务器还是缓存中获取,只要可以拿到,HTTP状态码都是200。
html页面
千万不要做强缓存
,以此来保证,即便本地有生效的缓存,但是只要服务器资源更新了,也要从服务器实时获取最新的资源进行渲染!- html文件不做缓存,但js及css及图片等要做。
- 我们平时开发的时候,需要基于webpack/vite等前端工具,对代码进行编译打包,把编译后的内容部署到服务器上!
- 在
webpack编译
的时候,我们可以设置一个规则:根据文件的内容,让生成的文件名带唯一hash值
。- 例如:
main.sae3fd9jk.js
。 - 在输入文件名称中加一个
[name].[hash:8].js
。
- 这样只要
代码修改
了,每次打包
都会创建不同的文件
出来,而html页面
中导入的也是最新的文件
!
- 例如:
- 这样只要html不做
强缓存
,就可以保证,服务器资源
一旦更新,我们获取的是最新资源
,而不是本地缓存
!
- 在
- 第一次访问网站,本地没有任何的缓存,需要向服务器发送请求;
协商缓存
:- 该做通信还是要做的,只不过可能要返回的数据变少了。
- 不用做处理,都能保证浏览器渲染的资源是服务器上最新的。
协商缓存
是强缓存
的一种补充。- 如果
强缓存
还在有效期,即便服务器设置了协商缓存
,那么协商缓存的机制
也不会触发。 - 只有没有设置强缓存或者强缓存失效了,设置的协商缓存机制才会生效!
- 如果
- 步骤:
- 第一次访问网站,本地啥缓存都没有,需要从服务器获取。
- 如果服务器想对当前的资源设置协商缓存,则在响应头中返回或设置相关的字段:
Last-Modified
:存储该资源在服务器最后一次修改的时间,HTTP/1.0
。ETag
:存储该资源在服务器中最后一次修改的标识(唯一的),HTTP/1.1
。
- 如果服务器想对当前的资源设置协商缓存,则在响应头中返回或设置相关的字段:
- 浏览器获取资源信息的同时,观察响应头信息,如果具备这两个字段,则把资源和标识都缓存在本地!
- 第二次访问网站,不论本地缓存是否生效。
- 前提是:强缓存肯定是没有或者失效了!
- 都需要重新向服务器发送请求。并且在请求头中携带两个字段:
If-Modified-Since
:存储的值是Last-Modified的值。If-None-Match
:存储的值是ETag的值。
- 服务器接收传递的信息及标识,用
If-Modified-Since
/If-None-Match
和当前服务器上最新的资源进行对比。- 对比后是一样的:说明服务器上的这个资源没有更改过,则服务器直接返回304-即
Not-Modified
,浏览器接收到这个状态码,则从本地缓存中获取资源信息进行渲染。 - 对比后是不一样的:说明服务器上的这个资源更改过,此时服务器返回状态码200、最新的资源信息、最新的
Last-Modified
/ETag
值!- 浏览器获取最新的信息后,除了渲染,再把最新的信息和标识缓存在本地!
- 对比后是一样的:说明服务器上的这个资源没有更改过,则服务器直接返回304-即
- 第一次访问网站,本地啥缓存都没有,需要从服务器获取。
- 建议:html不设置强缓存,只设置协商缓存。其它资源如js、css、图片等都设置强缓存及协商缓存。
- 强缓存:
- 针对ajax数据:本地存储。
- 基于ajax/fetch从服务器获取的数据,不能设置强缓存和协商缓存,如果需要对不经常更新的数据进行缓存,需要开发者基于本地存储进行处理!
- 最好的数据缓存方案是:vuex/redux;
- 既可以避免频繁向服务器发送请求,也可以保证在用户刷新后,可以及时从服务器获取到最新的信息。
- 但是对于一些不经常更新的数据,基于localStorage来存储-即自己设定时效性,也是不错的选择!
-
实现具备有效期的localStorage存储方案:
/* //实现具备有效期的localStorage存储方案; - localStorage.setItem()/localStorage.getItem()/localStorage.removeItem(); - 设置的值,都只能是字符串格式的; */ const storage = { // 存储信息的时候,记录一下存储的时间。 /** * @param {string} key * @param {any} value */ set(key, value) { let obj = { time: +new Date(), //存储时,当前日期的时间戳。 value: value, }; localStorage.setItem(key, JSON.stringify(obj)); }, // 获取存储信息的时候,判断一下时效性。 /** * @param {string} key */ get(key, expires = 30 * 24 * 60 * 60 * 1000) { let obj = localStorage.getItem(key); if (!obj) { return null; //传递的key压根不存在。 } let { time, value } = JSON.parse(obj); if (+new Date() - time > expires) { // 存储的信息已经地了指定的时效:移除存储的信息、返回null。 storage.remove(key); return null; } return value; }, // 移除指定信息。 /** * @param {string} key */ remove(key) { localStorage.removeItem(key); }, };
-
无缓存方案:
//无缓存方案: const query = async function query() { try { let result = await axios.get("/api/list", { params: { lx: "my", }, }); console.log(`result-->`, result); } catch (error) { console.log(`error-->`, error); } }; query()
-
有缓存方案-本地存储:
/* //实现具备有效期的localStorage存储方案; - localStorage.setItem()/localStorage.getItem()/localStorage.removeItem(); - 设置的值,都只能是字符串格式的; */ const storage = { // 存储信息的时候,记录一下存储的时间。 /** * @param {string} key * @param {any} value */ set(key, value) { let obj = { time: +new Date(), //存储时,当前日期的时间戳。 value: value, }; localStorage.setItem(key, JSON.stringify(obj)); }, // 获取存储信息的时候,判断一下时效性。 /** * @param {string} key */ get(key, expires = 30 * 24 * 60 * 60 * 1000) { let obj = localStorage.getItem(key); if (!obj) { return null; //传递的key压根不存在。 } let { time, value } = JSON.parse(obj); if (+new Date() - time > expires) { // 存储的信息已经地了指定的时效:移除存储的信息、返回null。 storage.remove(key); return null; } return value; }, // 移除指定信息。 /** * @param {string} key */ remove(key) { localStorage.removeItem(key); }, }; //有缓存方案-本地存储: const query = async function query() { let result = storage.get("CACHE", 7 * 24 * 60 * 60 * 1000); if (result) { //本地是具备有效缓存的,则停止向服务器发送请求。 console.log(`result-->`, result); return; } // 数据没有缓存过:则向服务器发送请求。 try { let result = await axios.get("/api/list", { params: { lx: "my", }, }); console.log(`result-->`, result); // 请求成功就:把请求的结果存储到本地。 storage.set("CACHE", result); } catch (error) { console.log(`error-->`, error); } }; query();
-
- 最好的数据缓存方案是:vuex/redux;
- 基于ajax/fetch从服务器获取的数据,不能设置强缓存和协商缓存,如果需要对不经常更新的数据进行缓存,需要开发者基于本地存储进行处理!
- 通过一个url网址,首先得到的是一个html。渲染过程中,遇到link标签向服务器发送拿到css代码,遇到script标签向服务器发送拿到js代码,遇到img标签向服务器发送拿到图片文件。
-
第三步:DNS解析
- 所谓DNS解析,就是去DNS服务器上,基于域名获取服务器的外网IP地址-即主机地址。
- DNS解析也是有缓存机制的:
- 比如谷歌浏览器记录
DNS解析
的缓存时间,大概是1分钟左右。 - dns缓存刷新时间是多久?dns本地缓存时间介绍
- 比如谷歌浏览器记录
- DNS解析步骤:
- 基于
递归查询
在本地缓存中查找DNS解析记录。- 这个是找缓存的方式的。
- 浏览器的DNS解析缓存。
- 本地Host文件。
- 关于修改host文件:
- 假设个人在本地host中加一条记录:
www.qq.com 127.0.0.1
- 导致:以后只要在这台电脑上访问
www.qq.com
,都相当于在访问127.0.0.1
。 - 我在本地启动一个项目 ,假设
http://127.0.0.1:80/index.html
为我开发的页面
。 - 后期我基于
http://www.qq.com
访问的也是本地的这个项目
。
- 很久之前,也是基于这个方式来解决跨域问题的。
- 开发环境上才用的,部署到同源环境下。
- 假设个人在本地host中加一条记录:
- 关于修改host文件:
- 本地DNS解析器缓存。
- 本地DNS服务器。
- 基于
迭代查询
到DNS服务器
上查找DNS解析记录
。- 根域名服务器。
- 顶级域名服务器。
- 权威域名服务器。
- 每一次DNS解析的时间,大概在20~120毫秒左右。
- 基于
- 单纯这样看,减少DNS的解析次数,会提高页面的加载速度!
-
想要减少DNS的解析次数,需要把所有的资源部署在相同服务器的相同服务下!
- 比如html与js与css与图片等,都放在同一台服务器的同一个服务下。
- 这样一台服务器可以访问一个页面,就要十多个请求进该服务器了。
- 在线人数一多,该服务器的压力就比较大。服务器就可能有并发压力,比如一台服务器只能同时处理2000多个请求。
- 所以在真实的项目中,我们往往要把不同的资源部署到不同的服务器上。
- 例如:
- 静态资源服务器。
- 图片和音视频服务器。
- 数据请求服务器。
- …
- 这样会导致网站中出现多个域名请求,也就是需要多个DNS解析!
- 这样做的好处:
- 资源的合理利用。
- 比如:
- 图片处理服务器:比较大,需要内存大的服务器。网络需要带宽大。
- 数据处理服务器:要处理的压力比较大,CPU要好。
- 比如:
- 降低单一服务器的压力,提高并发上限。
- …
- 资源的合理利用。
- 例如:
-
在DNS解析次数增加的情况下,我们可以基于dns-prefetch即DNS预解析,来提高网站的加载速度!
-
如:
<link rel="dns-prefetch" href="//dss0.bdstatic.com">
<link rel="dns-prefetch" href="//static.360buyimg.com">
- 浏览器解析时,看到link标签,会分配一个线程去解析该link标签。拿到DNS地址。
- 当渲染到img及script标签遇到那些域名时,就会使用之前所解析好的DNS地址去到指定IP地址去拿具体资源。
- 浏览器解析时,看到link标签,会分配一个线程去解析该link标签。拿到DNS地址。
-
-
…
-
DNS预解析的原理:
- 就是利用浏览器的多线程机制,在GUI渲染的同时,开辟新的线程去解析域名。
- 解析的结果会缓存在浏览器中。
- 这样当GUI渲染到一定的阶段,遇到新的资源请求的时候,可能域名已经解析过它了,直接用缓存中存储的外网IP,去服务器发送请求即可,不用再去DNS服务器中找到IP地址去解析了!
- 就是利用浏览器的多线程机制,在GUI渲染的同时,开辟新的线程去解析域名。
-
-
第四步:TCP三次握手
- 目的:根据DNS解析出来的服务器主机地址,建立起和服务器之间的传输通道。
- 为了保证传输通道的稳定性,需要进行
三次握手
。-
第一次:客户端向服务器发送一个信息,服务器接收请求。
- 对于客户端:
- 知道了客户端可以正常发信息。
- …
- 对于服务端:
- 知道了客户端可以正常发信息。
- 知道了服务器可以正常接收信息。
- …
- 对于客户端:
-
第二次:服务器向客户端发送一个信息,客户端接收请求。
- 对于客户端:
- 知道了服务器可以正常接收信息。
- 知道了服务器可以正常发信息。
- 知道了客户端可以正常接收信息。
- …
- 对于服务端:
- 知道了服务器可以正常发信息。
- …
- 对于客户端:
-
第三次:客户端向服务器发送一个信息,服务器接收请求。
- 对于客户端:
- …
- 对于服务端:
- 知道了客户端可以正常接收信息。
- …
- 对于客户端:
-
-
第五步:客户端和服务器之间的数据通信。
-
客户端向服务器发送请求,把一些信息传递给服务器。即Request请求阶段。
- 包含客户端信息的组成部分:
- 请求起始行:
请求方式
、请求地址
(包括问号传参信息
)、HTTP协议的版本。 - 请求头:也叫
请求首部
,各种各样的键值对
如token
、Content-Type
、Cookie
、User-Agent
、Authorization…。 - 请求主体(
Request Payload
):请求主体中的数据格式是有限制的。- 支持
字符串
:JSON格式的字符串
:Content-Type:application/json
。urlencoded格式的字符串
:如xxx=xxx&xxx=xxx
,Content-Type:application/x-www-form-urlencoded
。普通字符串
:Content-Type:text/plain
。
- 支持FormData格式对象:
Content-Type:multipart/form-data
。- 主要用于文件上传。
- 支持
Buffer
/二进制
等格式数据。 - …
- 不支持其余的对象格式
- 如果
请求主体
中,我们传递的是普通对象
,则浏览器默认会把其变为如"[object Object]"
的普通字符串传递给服务器。- 在axios内部,如果我们传递的是
普通对象
,其内部会默认把普通对象
变为JSON格式字符串
,基于请求主体
传递给服务器。
- 在axios内部,如果我们传递的是
- 如果
- 支持
- 请求起始行:
- 包含客户端信息的组成部分:
-
服务器
接收客户端的请求
,把客户端需要的信息
返回给客户端
。即Response响应阶段
。- 包含服务器信息的组成部分:
- 响应起始行:
HTTP协议的版本
、HTTP响应状态码
- 响应头:
Connection: keep-alive
、Date
-服务器时间
… - 响应主体:一般返回的都是
JSON格式字符串
,但也有可能是其它的格式
(- 例如:
xml
buffer
- …
- 例如:
- 响应起始行:
- 包含服务器信息的组成部分:
-
HTTP事务:
Request
+Response
。 -
HTTP报文:
请求起始行
/请求头
/请求主体
/响应起始行
/响应头
/响应主体
,统称为HTTP报文
!- 在控制台的Network中可查看详细
-
-
第六步:TCP四次挥手
- 目的:把建立好的传输通道释放掉。
- 流程:
- 当客户端把信息发送给服务器后,紧接着就发起了
第一次挥手
:我把信息给你了,你记得接收哈。 - 服务器接收到客户端的请求和传递的信息后:
- 服务器端发起
第二次挥手
:请求我收到了,你稍等一会吧,我现在去给你准备东西。 - 之后服务器根据客户端的信息:
- 准备客户端需要的信息。
- 把信息返回给客户端。
- 服务器端发起
第三次挥手
:东西已经合你了,你记得接收一下,我这边打算关闭通道了!
- 服务器端发起
- 客户端收到服务器返回的内容后,再次给服务器一个回馈(
第四次挥手
):东西我收到了,谢谢,我这边也关闭了。
- 当客户端把信息发送给服务器后,紧接着就发起了
- 如果每一次请求都
重新地进行
三握四挥,这样太浪费性能和时间了。- 我们期望第一次请求,把传输通道建立好后,当HTTP事务完成后,这个通道先不要关闭,保留一段时间,让后面的请求,继续基于这个通信通信即可!
- 解决方案:
请求头
/响应头
中设置Connection:keep-alive
,保持TCP通道的长链接
即可。- 在
HTTP/1.1
版本中,自动就设置了keep-alive长链接机制
,服务器端可以修改长链接的时间或者次数
。
- 在
- 解决方案:
- 我们期望第一次请求,把传输通道建立好后,当HTTP事务完成后,这个通道先不要关闭,保留一段时间,让后面的请求,继续基于这个通信通信即可!
-
第七步:客户端渲染
- 浏览器把服务器返回的信息(包括
html
/css
/js
/图片
/数据
等)进行渲染,最后绘制出对应的页面! - 步骤:
- 构建
DOM-Tree
即DOM树
。-
当从服务器获取HTML页面后,浏览器会分配GUI渲染线程。自上而下进行解析。
- 遇到
<link>标签
:分配一个新的HTTP网络线程去服务器获取样式资源,同时GUI继续向下渲染!- 不会阻碍GUI的渲染。
- 遇到
<style>标签
:虽然不需要去服务器获取样式,但是此时的样式是不渲染的,只有等待DOM-Tree构建完毕后,并且其余基于<link>标签
获取的样式也回来了,才会按照原定的顺序
(编写的顺序)进行渲染!- 为了保证样式的权重和先后顺序的覆盖。
- 遇到
@import
:也会分配一个HTTP线程去服务器获取样式资源,只不过其会阻碍GUI的渲染。- 只有等待样式资源获取完毕后,GUI才会继续向下渲染!
- 遇到
<img/>标签
/<audion>标签
/<video>标签
:和<link>标签
一致。分配一个新的HTTP网络线程去服务器获取样式资源,同时GUI继续向下渲染!- 但是对于
<audion>标签
/<video>标签
音视频来讲,如果设置了preload="none"属性,则开始渲染页面的时候,并不会去服务器获取音视频的资源。
- 但是对于
- 遇到
<script>...</script>
:立即把js代码执行。- 交给js引擎线程渲染,但是GUI会暂停,也相当于阻碍了GUI渲染。
- 遇到
<script src="">标签
:阻碍GUI渲染,分配一个新的HTTP线程去服务器获取js资源,等待js代码获取到之后,交由js引擎去渲染,只有js代码执行完毕,才会继续GUI的渲染! - 遇到
<script src="" async>标签
:分配一个新的HTTP线程去服务器获取js资源,此时GUI会继续渲染。但是等到js代码获取到之后,会立即停止GUI的渲染,先把js执行! - 遇到
<script src="" defer>标签
:和<link>标签
一致了。分配HTTP线程去服务器获取js资源,GUI会继续向下渲染。等待DOM-Tree渲染完毕,并且其它设置defer的js资源也获取到了,再按照编写的顺序,依次执行js!
- 遇到
-
当把所有的HTML结构解析完毕后,会构建出DOM元素之间的层级关系,这就是
DOM树
。DOM元素之间的层级关系
就是DOM树
。
-
在
DOM树
构建完毕后,会触发一个事件:DOMContentLoaded()。window.addEventListener(`DOMContentLoaded`,function(){ //此函数中,一定可以获取到页面中已经渲染出来的DOM元素。 //... })
- 只能用DOM2级事件进行绑定。
-
- 构建
CSSOM-Tree
即样式树
。- 等待样式资源从服务器获取到之后,会按照既定的顺序,开始渲染css样式,最后生成
CSSOM-Tree
! - css样式被称之为层叠样式表,就是因为其具备:层级、权重、继承的。
- 等待样式资源从服务器获取到之后,会按照既定的顺序,开始渲染css样式,最后生成
- 把
DOM-Tree
与CSSOM-Tree
合并在一直,创建Render-Tree
即渲染树
。- 渲染树中,已经非常清楚地知道了:DOM元素的层级和每一个元素的样式。
- Layout布局排列计算-对应
回流
/重排
-Reflow
。- 根据当前视口的大小,计算元素在视口的位置,以及相关的样式!
- 分层。
- 在一个页面中,不仅仅只有一层,它是一个3D空间,会存在很多层。这就是所谓的脱离文档流。
- 而分层的目的就是:把每一层及每一层中的元素样式都详细地规划好,包括具体的绘制步骤。
- Painting绘制-对应
重绘Repainting
。- 开始
一层层
地按照规划好的步骤
进行绘制,直到整个页面都被绘制完毕!
- 开始
- 构建
- 浏览器把服务器返回的信息(包括
-
根据这种HTTP网络进行优化,这就是CRP关键渲染路径。
- CRP(Critical [ˈkrɪtɪkl] Rendering [ˈrendərɪŋ] Path)。
辅助知识
TCP与UDP
- TCP与UDP
- TCP经过三次握手,建立起一个
稳定可靠
的传输通道,但是因为第一次的通信,都需要经过三次握手,确保通道稳定后再传输,这样导致传输效率慢!- 一般都用的TCP。
- UDP是不需要经过三次握手的,每一次的传输都是直接进行,这样保证传输的效率,但是不能保证传输的稳定性!
- 直播一般用的是UDP,用于保证传输效率高,以便速度快,降低延迟。
- TCP经过三次握手,建立起一个
看到客户端和服务器之间所有的通信信息
- 控制台上的Network可以看到客户端和服务器之间所有的通信信息。
- 我们可以以此进行前后端通信的调用:
- 请求没发:前端问题-没发送请求。
- 请求不成功:
- 看前端传给后端的字段是否正确。
- 前端的问题-传参字段不正确。
- 传参字段正确后看返回结果。
- 返回结果不正确-后端问题。
- 看前端传给后端的字段是否正确。
- 返回结果正确,但没预期效果。
- 前端问题,没处理好后端返回的代码。
- 我们可以以此进行前后端通信的调用:
常用的请求方式
- 常用的请求方式:
-
GET系列: get、head、delete、options。
- head:一般只获取请求及响应头部信息。
- delete:一般用于删除一个文件。
- options:试探性请求,cors跨域时用到。
-
POST系列:post、put、patch。
- put:一般用于向服务器添加一些请求。
- patch:用于对比数据。
-
平时开发的时候,大家约定俗成的规范:
- 基于get系列发送请求,我们把传递的信息以问号传参的方式发送给服务器。
- 如果是基于post系列发送请求,则我们把传递的信息,基于请求主体发送给服务器。
-
get请求与post请求的区别:
-
传输信息的大小:
- 浏览器对于URL地址的长度是有限制的,所以基于get方式发送请求,需要把传递的信息,以问题传参的方式,拼接到url的末尾,这样对于传递信息的大小就有了限制!
- 2023年:
- IE 2kb。
- 谷歌 8kb。
- 为什么浏览器要限制url的长度?
- 因为url地址浏览器可能要记录,如果太长,历史记录就会占用太多空间。
- 2023年:
- 而post系列请求,是基于请求主体把信息给服务器,请求主体理论上是没有大小限制的。
- 实际开发中我们会自行限制!
- 因为数据越大,传输数据的一个请求的所需时间会太长。
- 实际开发中我们会自行限制!
- 浏览器对于URL地址的长度是有限制的,所以基于get方式发送请求,需要把传递的信息,以问题传参的方式,拼接到url的末尾,这样对于传递信息的大小就有了限制!
-
get请求容易产生缓存,而post请求不会。
- 问题描述:如果很短的时间内,向服务器发送多个请求,这些请求的地址及传参信息都一模一样,那么后面请求很有可能获取的是第一个请求缓存的信息!
-
解决方案:只要保证每一次请求传递的参数不完全一致即可!如多加一个参数
_=随机数/时间戳
。xhr.open(`GET`,`/api/list?lx=1&name=AA&_=${+new Date()}`)
-
- 问题描述:如果很短的时间内,向服务器发送多个请求,这些请求的地址及传参信息都一模一样,那么后面请求很有可能获取的是第一个请求缓存的信息!
-
get请求相对于post请求来讲,是不安全的。
- 但是post也不安全,互联网面前人人都在裸奔。
- 不论是何种请求方式,请记住,他们都是明文传输,所以对于重要的信息,一定要记得加密!
- 有一个非常简单的黑客技术:URL劫持。
- 但是post也不安全,互联网面前人人都在裸奔。
-
-
常见的HTTP状态码
- 常见的HTTP状态码
- 200 成功。
- 206 断点续传。
- 301 永久重定向。
- 一般用于域名迁移。
- 302/307 临时转移/重定向。
- 一般用于服务器的负载均衡。
- 304 协商缓存-服务器资源没有更新。
- 400 请求参数有误。
- 401 无权限访问。
- 403 服务器拒绝访问-原因没有告知。
- 404 请求地址不存在。
- 405 请求方式不被允许。
- 408 请求超时。
- 前端设置了超时时间,但该时间内,后端没返回。
- 500 未知的服务器错误。
- 502 服务器网关错误。
- 503 服务器超负荷。
平时项目开发中的一些优化技巧
- 平时项目开发中的一些优化技巧
- 建议把
<script>标签
都放在页面的底部,防止其阻碍GUI的渲染,而且在js中也可以获取到渲染后的DOM元素了! - 如果我把
<script>标签
放在了页面顶部,此时在js中是不能直接拿到DOM元素的,该如何解决?-
说明:
-
有如下代码:
-
index.html
<!DOCTYPE html> <html> <body> <div id="box">哈哈哈</div> </body> <script src="./1.js"></script> </html>
-
1.js
const box = document.querySelector('#box') console.log(box)
-
-
把下方代码成为上方代码的效果,即可以拿到DOM。
-
index.html
<!DOCTYPE html> <html> <script src="./1.js"></script> <body> <div id="box">哈哈哈</div> </body> </html>
-
1.js
const box = document.querySelector('#box') console.log(box)
-
-
-
设置defer属性。
- 代码:
-
index.html
<!DOCTYPE html> <html> <script src="./1.js" defer></script> <body> <div id="box">哈哈哈</div> </body> </html>
-
1.js
const box = document.querySelector('#box') console.log(box)
-
- 代码:
-
可以监听DOMContentLoaded/load事件,在事件中去获取DOM元素。
-
代码:
-
index.html
<!DOCTYPE html> <html> <script src="./1.js" defer></script> <body> <div id="box">哈哈哈</div> </body> </html>
-
1.js
window.addEventListener('DOMContentLoaded', function () { const box = document.querySelector('#box') console.log(box) })
或:
window.addEventListener('load', function () { const box = document.querySelector('#box') console.log(box) })
-
-
DOMContentLoaded:只要DOM-Tree构建完毕后就触发。
-
load:只有所有资源都加载完毕才会触发。
- 如css或图片或js文件等。
-
-
- 平时开发的时候。
- 如果把
<script>标签
都放在了页面底部,那么defer/async都可以不加。 - 如果没有都放在底部,我们可以设置defer/async来减轻对GUI的阻碍。
- 如果js之间没有依赖关系,设置async即可,谁先获取到资源,就先把谁执行!
- 如果之间是存在依赖的,那么只能设置defer,要等待所有js资源都获取到,再按照编写的顺序依次执行!
- 如果把
- 对于样式的处理,坚决不用@import导入式。
- 除less/sass中的@import。
- 如果在
<link>标签
外链式的情况下,也可以使用@import导入式。
- 剩下的情况,对于
<link>标签
:- 如果样式代码比较少,建议使用内嵌样式。
- 尤其是移动端。
- 样式代码较多的情况下,还是使用外链式更好一些!
- 如果样式代码比较少,建议使用内嵌样式。
- 而且要把
<link>标签
放在页面头部,让GUI和HTTP同时工作,加快页面的整体渲染进度。 - 同源下,我们可以同时分配的HTTP线程数量,最多是5~7个。
- css和js建议进行合并压缩。
- 第一次渲染页面的时候,对于图片/音频/视频等资源,一定要开启懒加载,不要让这些资源占据有限的HTTP线程数。并且第一次渲染如果不渲染图片,页面绘制的速度会更快!
- 建议把
前端性能优化
重排和重绘
- 重排(回流)和重绘
- 页面第一次渲染,一定会触发一次重排(Layout)和重绘(Painting)
- 重排(回流):根据视口大小,计算元素在视口中的位置和样式。
- 当我们进行了修改了视口大小、修改了元素的位置、修改了元素大小、删除/新增元素等操作,浏览器会重新计算所有元素在视口中的位置和样式(针对某一层处理),这就是重排/回流-Reflow。
- 而且重排之后,一定会触发重绘。
- 所以重排这个操作是非常消耗性能的!
- 这也是操作DOM消耗性能的原因。
- 当我们进行了修改了视口大小、修改了元素的位置、修改了元素大小、删除/新增元素等操作,浏览器会重新计算所有元素在视口中的位置和样式(针对某一层处理),这就是重排/回流-Reflow。
- 重绘:按照规划好的样式,绘制页面。
- 当我们修改了元素的背景颜色、文字颜色、透明度等样式,这些样式不会改变元素的位置和大小,不需要重排,只需要重新绘制相应的元素即可。
- 如果想让页面中有变化,重绘是必不可免的!
- 所以真实项目中,减少DIM的重排(回流)是非常重要的性能优化手段!
- 当代浏览器只会对一个层面的元素进行处理。
- 具体优化操作:
-
不直接操作DOM,而是使用Vue/React等框架,基于数据驱动视图渲染。而框架内部在处理真实DOM的时候,已经尽可能减少了DOM重排的问题!
- 底层机制是虚拟DOM -> 真实DOM。
-
利用浏览器的渲染队列机制,把元素样式的读写操作进行分离。
box.style.width='200px' box.style.height='100px' box.style.margin='0 auto' //这样操作只会引发`一次`重排。
- 这样操作只会引发
一次
重排:遇到每一行修改样式的代码,浏览器并没有立即渲染,而是把其加入到渲染队列中,等待同步操作结束后,再把渲染队列中的,修改样式的操作,统一渲染一次!
box.style.width='200px' console.log(box.offsetWidth) box.style.height='100px' console.log(box.offsetHeight) box.style.margin='0 auto' //这样操作会触发`三次`重排。
- 这样操作会触发
三次
重排:每当遇到获取元素样式的操作,会立即刷新渲染队列。
box.style.width='200px' box.style.height='100px' box.style.margin='0 auto' console.log(box.offsetWidth) console.log(box.offsetHeight) //这样操作只会触发`一次`重排。
- 上方的操作就是所谓的读写分离。
box.style.cssText='width:200px;height:100px;margin:0 auto;' console.log(box.offsetWidth) console.log(box.offsetHeight) //这样操作只会触发`一次`重排。
- 这样操作只会触发
一次
重排,或者基于修改
- 这样操作只会引发
-
新增元素的时候,要采用统一批处理的方式。
let arr = new Array(10).fill(null) arr.forEach(()=>{ let div=document.createElement('div') document.body.appendChild(div) }) //这样会触发`十次`重排
let str = `` let arr = new Array(10).fill(null) arr.forEach(()=>{ str += `<div> ... </div>` }) document.body.innerHTML+=str //这样只会触发`一次`重排
- 这样只会触发
一次
重排;- 只不过这个方案,会对body中原有元素产生影响;
- 比如:原有元素绑定的事件都没了;
- 原因是因为,里面的元素是通过
document.body.innerHTML
转成了html字符串
,之后再用该html字符串
拼接上新的html字符串变成的。已经不是之前绑定了事件的DOM元素了。 - 不过
document.body
依旧是之前的DOM元素,如果是对document.body
进行事件委托的话,是不会有问题的。
- 只不过这个方案,会对body中原有元素产生影响;
let frag=document.createDocumentFragment() //创建一个文档碎片(临时存放DOM元素的容器) arr.forEach(()=>{ let div=document.createElement('div') frag.appendChild(div) //把每一轮循环创建的元素,先放在文档碎片中 }) document.body.appendChild(frag) //最后把整个文档碎片插入到页面中 // 这样会触发“一次”重排
- 这样只会触发
-
在js中尽可能使用transform变形属性,来修改元素的样式,因为修改transform样式,不会引发重排!
-
把要修改样式的元素(尤其是需要频繁修改的,例如:js动画),放在单独的文档流中!这样即便发生重排,也是对当前层处理,对其它层没有影响!
-
在实现动画的时候,我们可以牺牲平滑度来换取速度!
-
…
-