彻底掌握基于HTTP网络层的 “前端性能优化“

[前言]

如何发布自己的网站

发布自己的个人站「前提:做一个自己的个人作品」

1.购买一台服务器

  • @1 购买一台服务器:阿里云
  •     外网IP:123.57.205.204
  •     我们基于FTP上传工具「FileZilla」: 把自己本地开发的代码及文件上传到服务器上
  •     基于nginx、apache、iis等把作品进行发布:在服务器端创建一个web服务,让其管理项目资源
  •     后期别人基于 IP地址+端口号 就可以访问到我们的内容了

2.购买域名

  • @2 购买域名「作用:给不好记忆的服务器外网IP设置一个好记忆的名字」
  •     域名解析:让我们购买的域名和服务器的外网IP可以关联在一起
  •     把解析记录放在DNS服务器上
  •     备案

前端性能优化的思想:CRP关键渲染路径  围绕浏览器的渲染机制,针对于每一个细小的渲染环节进行优化

HTTP事物:包含一个完整的请求「request」和响应「response」

HTTP报文:客户端和服务器端传输的所有内容统称为HTTP报文

客户端传递给服务器的

  •      1. 请求起始行
  •        请求方式  使用的HTTP版本号
  •      2. 请求首部(头) Request Headers
  •        Cookie、User-Agent、Host、Cache-Control、If-Modified-Since、If-None-Match、Content-Type...
  •        客户端可以基于请求头,把一些信息传递给服务器,浏览器自己会默认带一些请求头信息给服务器
  •      3.请求主体

   服务器返回给客户端的

  •      1.响应起始行
  •        HTTP版本号  响应的HTTP状态码及其描述
  •      2.响应头 Response Headers
  •        Date服务器时间、Server、Connection、Keep-Alive、ETag、Last-Modified、Cache-Control、Expires...
  •      3. 响应主体 Response

谷歌浏览器控制台 -> network(网络) -> 可以查看所有客户端发送的请求以及传输的报文信息

从输入URL地址到看到页面中间都经历了啥?

第一步:URL地址解析

彻底掌握基于HTTP网络层的 “前端性能优化“_第1张图片

  @1 传输协议「作用:用户客户端和服务器端的信息传输」

基于它实现客户端和服务器的数据通信(快递小哥)

  •       HTTP:超文本传输协议「最常用的」除了传输文本外,还可以传输音视频,图片等富文本资源
  •       HTTPS:HTTP+SSL 比HTTP更安全的传输方式「涉及支付类的网站基本上都是HTTPS协议」
  •       FTP:把本地开发的资源上传到服务器

   @2 域名

就是一个好记的名字,域名解析:向DNS服务器上加入一条记录, 域名 主机地址

  •       顶级域名  qq.com 「购买」
  •       一级域名  www.qq.com
  •       二级域名  sports.qq.com
  •       三级域名  kbs.sports.qq.com

    @3 端口号「作用:区分相同服务器上的不同服务(项目)的  0~65535

  •       浏览器地址栏中输入一个URL地址,浏览器有一个默认操作,如果我们自己没有写端口号,浏览器会根据传输协议自己加上默认的端口号
  •       http -> 80
  •       https -> 443    https://www.baidu.com/ -> https://www.baidu.com:443/
  •       ftp -> 21

    @4 请求资源的路径名称「如果不写,则默认一般都是index.html(服务器可以自己配置)」

    @5 问号传参 查询字符串

  •        + 客户端基于"问号参数"把信息传递给服务器(Get请求经常这么干)
  •        + 在页面跳转的过程中,基于问号参数传递个另外一个页面一些信息(或者两个组件),实现信息传输
  •        + ...

    @6 HASH(哈希)值

  •        + 锚点定位
  •        + 基于哈希值实现HASH路由

 扩展:如果URL中如果出现了中文或者某些特殊符号,为了防止传输过程中乱码,则需要进行加密(对称加密)/解密

  • 对整个URL 加密encodelURI/decodeURI,会对空格以及一些中文字符加密
  • 对url查询字符串中传递的值进行单独加密 encodeURIComponent / decodeURIComponent 在encodelURI的基础上还可以对://#@等特殊符号进行加密,所以不用其处理整个URL,只是处理传递参数的一部分;/值
  • escpe/unescpe

第二步:缓存检查

缓存位置「Memory Cache虚拟内存缓存(内存条)、Disk Cache物理内存缓存 (硬盘 )

最开始从服务器获取信息,如果存在缓存机制,则会在Memory和Disk中各存储一份

  •    物理内存可以持久化存储,但是虚拟内存页面关闭后,存储的信息就被释放掉了
  •    普通刷新(F5):从Memory Cache中获取,没有找到在找Disk Cache
  •   页面关闭再重新打开:从Disk Cache中获取
  •   强制刷新(CTRL+F5):清除本地缓存,重新从服务器获取最新的内容(不找任何缓存)

@1 强缓存

本地存在缓存(且未过期),则直接从本地缓存中获取渲染{不论服务器端是否更新了内容};如果没有缓存(或者过期了)才从服务器重新获取;

实现的步骤:

由服务器设置,客户端浏览器去执行,无需前端写啥代码,

 第一次向服务器发送请求(本地没缓存),服务器把信息返回给客户端「同时在响应头中设置 Expires或Cache-Control,用来规定是否设置强缓存以及缓存的周期」; 客户端获取信息后,渲染的同时,根据缓存的规则,把信息缓存在本地;

        + cache-control: max-age=2592000  单位秒   HTTP1.1 指定过期时间

        + expires: Tue, 07 Dec 2021 15:14:39 GMT HTTP1.0  具体有效期

        + 两个都返回,按照HTTP版本的支持度,取最高版本支持的项

    第二次向服务器发送请求,先看本地是否有缓存以及是否过期,有且未过期,直接读取缓存的信息,如果缓存失效,则从服务器重新获取...重新获取后再把最新的内容缓存在本地!

强缓存如何设置:是由服务器端在响应头中基于Expires和Cache-Control设置缓存规则的,而客户端自己会根据这些规则,完成强缓存的存储及校验等

强缓存的作用:保证第二次及以后访问资源更快,对第一次没啥特殊效果!!

强缓存的副作用:在缓存有效期内,服务器资源更新了,我们也是获取的本地缓存信息,无法获取最新的信息

  • 加载一个页面,首先获取的是html,在浏览器渲染html的时候,遇到link/script/img等标签,才会从服务器获取这些资源信息!!
  •   所以真实项目中,html页面是“绝对不能设置强缓存的”,一但html都缓存了,则和服务器彻底失联了,服务器不论如何更新,客户端在缓存有效期内都无法获取最新的!!
  •   服务器端只要代码更改,我们让其重新生成一个新的文件名「webpack」,在html中导入新的文件名

彻底掌握基于HTTP网络层的 “前端性能优化“_第2张图片

强缓存特点:不论是从服务器获取的还是从缓存中读取的,返回的状态码都是200彻底掌握基于HTTP网络层的 “前端性能优化“_第3张图片

@2 协商缓存

  每一次请求都要和服务器进行协商(看服务器资源是否更新,如果更新,直接获取最新的,如果没有更新,则获取缓存)  如果强缓存和协商缓存都设置了,必须等待强缓存失效后,才会执行协商缓存

都会产生一个etag的值 例如61a48a78-11d0"

服务器最后更新时间存储到last-modified 例如Mon, 29 Nov 2021 08:08:24 GMT

每一次请求都要问服务器是否更新,所以每次可以保证获取最新资源,但是不如强缓存的效率高,真实项目中我们一般都是HTML只做协商缓存其余资源既做强缓存也做协商缓存,这样强缓存失效后还可以基于协商缓存二次进行处理

实现步骤

  •  每一次发送请求,在请求头中携带If-None-Match(存储的是etag的值)或者if-Modified-Since(存储的是last-modified)的值 给服务器:服务器接收到请求,会拿传递过来的值和服务器上的资源最后更新的标识和时间做对比
  •  第一次向服务器发送请求,服务器返回对应的资源信息「同时在响应头中返回两个字段:last-modified(HTTP1.0 当前资源最后一次更新的时间) & etag(HTTP1.1 只要服务器资源更新就会产生一个唯一的etag值)」,客户端获取信息后,返现存在这两个字段则会在客户端本地存储资源信息及相关标识字段
  •    第二次发请求,即便发现本地有缓存信息,也需要先和服务器协商「协商内容,问一下服务器,这个资源你有没有再更新过」:把之前存储的last-modified和etag基于请求头中的If-Modified-Since和If-None-Match发送给服务器(浏览器自己会完成),服务器根据传递过来的文件更新时间(标识)和目前服务器最新的更新时间(标识)去对比
  •       + 两次时间(标识)一致,说明服务器资源距离上一次缓存没有任何的更新,此时服务器返回304状态码即可,客户端接收到这个状态后,从本次缓存的信息中获取渲染即可
  •       + 如果不一致,说明更新过,返回状态码200以及最新的信息(和新的last-modified&etag),客户端把最新的信息获取渲染及再次缓存
  • 协商缓存也是由服务器设置的:在响应头中返回last-modified&etag
  • 协商缓存是对强缓存的一种补充:对于html页面无法设置强缓存但是可以设置协商缓存、而且在强缓存失效的情况下,我们依然可以基于协商缓存验证一下服务器是否更新

彻底掌握基于HTTP网络层的 “前端性能优化“_第4张图片

----> 不论强缓存还是协商缓存,都是服务器设置的「在响应头返回对应的字段」,而客户端需要做的事情,浏览器自己就处理了,不需要我们前端写啥代码;而且都是针对于静态资源文件的缓存设置(例如:/         html/css/js/图片...),对于ajax数据请求,强缓存和协商缓存是无效的...

强缓存字段:cache-control 指定过期时间   expires具体有效期

协商缓存字段:last-modified服务器最后更新时间,  etag唯一标识

@3 数据缓存「ajax数据通信的时候,对于不经常更新的数据,我们基于本地存储实现缓存」

需求:本地有缓存且未过期,则从本地获取,本地缓存实失效,则从服务器获取(类似于强缓存),需要基于js存储方案以及相关的操作去编码实现

  • 第一次本地没有存储过任何信息,此时向服务器发送ajax请求,从服务器获取数据,获取数据后一方面进行数据绑定和渲染,另外一方面把数据存储到本地「基于本地存储方案、设置有效期」
  • 后期再次发送请求,首先看本地是否存储过这些数据,且是否过期
  •   + 存储过且未过期:不需要从服务器获取,只需要获取本地存储的内容即可
  •   + 未存储或者过期:重新向服务器发送请求获取最新的信息,同时本地也把最新内容存储
  • 数据缓存需要前端开发工程师基于 JS代码+本地存储方法 自己去实现

本地存储方案:把一些信息存储到本地   谷歌浏览器 -> Application -> Storage

特点:明文存储而且可以被查看(所以重要敏感信息不要存储,即便存储也要加密);而且存储的信息都是以字符串类型进行存储的「排除虚拟内存存储」;有源的限制(在某个域下存储的信息,只能在本域中获取);

  @1 cookie本地存储

  • 操作:document.cookie
  •     + 兼容所有的浏览器,是传统的本地存储方案
  •     + 存储内容有大小有限制:同源下最多允许存储4KB
  •     + 具备存储有效期,需要自己去设置
  •     + 不稳定:清除历史记录或者电脑垃圾可能会清除掉cookie、以及网站的隐私/无痕模式下是禁止存储cookie的
  •     + cookie不是单纯的本地存储,和服务器之间有“猫腻”:只要本地存储cookie,不论服务器需不需要,每一次请求都会基于请求头中的cookie字段,把存储的信息传递给服务器(如果存储东西多,导致每一次请求都会变慢);服务器只要在响应头中设置set-cookie字段,浏览器自己就会在本地设置cookie;

  @2 localStorage本地存储

实现具备有效期的localStorage,存储信息的时候,多存储一个时间,后期获取的时候拿当前时间-存储的时间,验证差值是否在你规定的有效期内即可

  • 操作:localStorage.setItem([key],[value])/getItem([key])/removeItem([key])/clear()
  •     + H5中新增的API,不兼容IE6~8
  •     + 存储内容有大小有限制:同源下最多允许存储5MB
  •     + 持久化存储,除非手动删除,否则一直存着,不具备有效期
  •     + 相对稳定,干掉cookie的那些操作暂时都干不掉localStorage
  •     + 绝对的本地存储,和服务器之间没有任何的关系「我们可以“手动”把本地存储的信息传递给服务器」

代码实现

 async created(){
        //检验本地缓存是否生效
        let newsBefore = localStorage.getItem("newsBefore")
        if (newsBefore) {
            //变成对象
            newsBefore = JSON.parse(newsBefore)
            if (+new Date() - newsBefore.item < 3600000) {
                console.log("成功[缓存]", newsBefore.data);
                result
            }
        }
        //本地缓存失效从服务器获取&存储本地缓存
        let result = await this.$api.queryNewsbefore('202220116')
        console.log("成功,服务器", result);
        localStorage.setItem("newsBefore", JSON.stringify({
            time: +new Date(),
            data: result
        }))
    }

封装 存储方案:基于localStorage实现数据缓存

  //func这个方法可以像服务器发请求,返回一个promise实例,并且根距请求结果决定实例状态
    // name localStorage存储时的key
    // limit:有效期,默认一小时单位是毫秒3600000
    export const cacheStore = function (func, name, limit) {
        if (typeof func !== "function") throw new TypeError('func is not a function');
        if (typeof name !== "string") throw new TypeError('name is not a string');
        if (typeof limit !== "number" || isNaN(limit)) limit = 3600000;
        return new Promise(async (resolve, reject) => {
            let result = localStorage.getItem(name),
                now = +new Date(),
            if (result) {
                let { time, data } = JSON.parse(result);
                if (now - time < limit) {
                    //缓存有效
                    resolve(data)
                    return
                }
            }
            //缓存失效
            try {
                result = await func()
                localStorage.setItem(name, JSON.stringify({
                    time: new Date(),
                    data: result
                }))
            } catch (err) {
                reject(err)
            }
        });
    }

  @3 sessionStorage会话存储

  •  操作:和localStorage一样
  •     + 不论是localStorage还是cookie,只要本地存储了信息,页面刷新或者页面关闭后重新打开,存储的信息都在;但是sessionStorage属于会话存储,页面刷新存储的信息是存在的,因为会话还没结束;但是一但页面关闭,会话结束,则存储的信息就会释放掉!!

  @4 vuex/redux全局变量

+ 他们类似于在全局上下文中创建全局变量(或者容器)来存储信息,所以只有页面刷新或者关闭,之前存储的信息都会释放掉!!

  @5 IndexedDB  

  @6 WebSQL

@5和@6都是本地数据库存储,很不常用

彻底掌握基于HTTP网络层的 “前端性能优化“_第5张图片

 第三步:DNS解析( 域名解析)

  域名解析,基于域名到DNS服务器上进行查找,找到服务器的外网IP找到服务器「每一次解析大概需要20~120ms左右」

  @1 递归查询

        从本地获取DNS解析记录(本地的解析记录一般是缓存下来的,也有自己手动配置的)

       浏览器的DNS解析缓存 -> 本地HOSTS文件 -> 本地DNS解析器缓存 -> 本地DNS服务器上

彻底掌握基于HTTP网络层的 “前端性能优化“_第6张图片

  @2 迭代查询

        本地没有找到解析记录,才会去公网上查询

       根域名服务器 -> 顶级域名服务器 -> 权威域名服务器

彻底掌握基于HTTP网络层的 “前端性能优化“_第7张图片

 项目优化:提高域名的解析效率/速度

  •   把资源放在相同服务器的相同服务下,确保页面中域名解析的次数少;这样的操作,虽然优化了DNS的解析,但是可能导致服务器压力过大,服务器处理速度变慢,导致网站整体性能更差!!
  •     所以真实项目中,我们宁愿增加域名解析的次数,也会把不同的资源分散到不同的服务器上部署!!
  •        + web服务器:处理html/css/js...静态资源
  •        + 数据服务器:处理ajax请求的、实现数据管控和业务逻辑的
  •        + 图片/音视频服务器:处理富媒体资源的
  •        + ...
  •   在域名解析增加的情况下,我们可以基于 DNS Prefetch(DNS预解析) 来优化域名解析,原理:利用link标签的异步处理,在GUI主线程渲染的过程中,同时去预先完成DNS解析,这样后续到了指定资源请求的时候,DNS可能已经解析过了,此时我们从本地缓存中获取解析记录即可,也优化了页面渲染的速度!!

彻底掌握基于HTTP网络层的 “前端性能优化“_第8张图片彻底掌握基于HTTP网络层的 “前端性能优化“_第9张图片

 服务器拆分的优势

  • 资源的合理利用
  • 抗压能力加强
  • 提高HTTP并发、同源 http 5-7
  • ……

第四步:TCP三次握手「作用:建立客户端和服务器端的链接通道」

在拿到外网IP后,我们建立建立客户端和服务器端的链接通道 TCP通道

  •      通信协议:TCP「安全稳定可靠」、UDP「快」
  •     TCP需要经历三次握手,才建立通道,虽然通道稳定,但是消耗时间
  •     UDP无需经历三次握手,直接传输,这样可能会“丢包”,但是快... 音视频
seq序号:用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标识
ack确认序号:只有ACK标志位为1时,确认序号字段才有效,ack=seq+1
标志位:
	ACK:确认序号有效
	RST:重置连接
	SYN:发起一个新连接
	FIN:释放一个连接

建立稳定链接通道

  • 第一次: 客户端发一个新的链接 SYN 并且携带一个序列号 seq(服务器你在嘛~~)
  • 第二次 服务器向客户端发个新的请求 把确认序列号ACK返回去在传一个新的序列号给客户端(我在呢 客户端~~)
  • 第三次 收到ACK确认序列号 在把之前的seq+1给服务器 建立链接完成(知道了 开始传输把~~)

彻底掌握基于HTTP网络层的 “前端性能优化“_第10张图片

三次握手为什么不用两次,或者四次?

TCP作为一种可靠传输控制协议,其核心思想:既要保证数据可靠传输,又要提高传输的效率!

第五步:基于HTTP/HTTPS等实现数据通信

HTTP/HTTPS在建立好的链接通道来传输客户端和服务器端的信息

  HTTP事物

  •   HTTP报文:请求报文 & 响应报文
  •   请求方式:GET/POST/DELETE/PUT/HEAD/PATCH/OPTIONS...
  •   HTTP状态码:200 301 302 307 304 400 401 403 404 500 503 ...
  •   请求主体&响应主体的数据格式要求

  ...

第六步:TCP四次挥手「作用:把建立的链接通道释放掉」

  • 客户端把请求报文给服务器了,然后释放通道
  • 第一步 :客户端:我把我的东西都给你了,我要关闭通道了
  • 第二步: 服务器准备客户端想要的东西 ,立级告诉客户端我收到了,我正在准备,你等会儿~
  • 第三步:我把东西都准备好了你准备接收把,我也要释放通道了
  • 第四步 我收到了释放通道把
  • 如果每一次请求都需要重新的“TCP三握四挥”,很耽误时间也很消耗性能,我们最好保持TCP通道的持久性->“长链接”
  •     服务器在响应头中设置 Connection: keep-alive
  •     Keep-Alive: timeout=15, max=300 设定长链接的周期
  •     浏览器也在请求头中设置 Connection: keep-alive 「自动的」

彻底掌握基于HTTP网络层的 “前端性能优化“_第11张图片

 为什么连接的时候是三次握手,关闭的时候却是四次握手?

  • 服务器端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文
  • 但关闭连接时,当服务器端收到FIN报文时,很可能并不会立即关闭链接,所以只能先回复一个ACK报文,告诉客户端:”你发的FIN报文我收到了”,只有等到服务器端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送,故需要四步握手。

第七步:浏览器渲染获取的代码和内容

浏览器底层渲染机制 前端优化总结

HTTP1.0 VS HTTP1.1 VS HTTP2.0

HTTP1.0和HTTP1.1的一些区别

  • 缓存处理,HTTP1.0中主要使用 Last-Modified,Expires 来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略:ETag,Cache-Control…
  • 带宽优化及网络连接的使用,HTTP1.1支持断点续传,即返回码是206(Partial Content)
  • 错误通知的管理,在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除…
  • Host头处理,在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)
  • 长连接,HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点

HTTP2.0和HTTP1.X相比的新特性

  • 新的二进制格式(Binary Format),HTTP1.x的解析是基于文本,基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合,基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮
  • header压缩,HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小
  • 服务端推送(server push),例如我的网页有一个sytle.css的请求,在客户端收到sytle.css数据的同时,服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了
// 通过在应用生成HTTP响应头信息中设置Link命令
Link: ; rel=preload; as=style, ; rel=preload; as=image
  • 多路复用(MultiPlexing)

- HTTP/1.0  每次请求响应,建立一个TCP连接,用完关闭
- HTTP/1.1 「长连接」 若干个请求排队串行化单线程处理,后面的请求等待前面请求的返回才能获得执行机会,一旦有某请求超时等,后续请求只能被阻塞,毫无办法,也就是人们常说的线头阻塞;
- HTTP/2.0 「多路复用」多个请求可同时在一个连接上并行执行,某个请求任务耗时严重,不会影响到其它连接的正常执行;

你可能感兴趣的:(http,前端,性能优化)