提升项目档次-资源处理

更多文章

前言

前端发展的真滴快,无论从面试、webpack、还是各大框架打包编译及打包结构都可以看到一些对资源优化的蛛丝马迹,最经典的面试题就是:url输入到页面呈现过程,除了考察过程本身,更主要的是这个过程的优化,以此过程来聊一下如何处理资源

DNS

这里不说DNS怎么解析的,DNS本身解析的过程成也会有各种缓存,比如:浏览器缓存、系统缓存、路由器缓存等,这个过程前端还是蛮难去接触到,但是DNS解析的越快页面呈现的也越快,这个是毫无疑问的,所以前端能做的就是帮助尽快去解析DNS

这里就涉及到了DNS的预解析(多页面慎用):

<!-- 告知浏览器页面DNS需要预解析 -->
<meta http-equiv="x-dns-prefetch-control" content="on" />
<!-- 强制解析 -->
<link rel="dns-prefetch" href="//www.chasejourney.top" />
<link rel="dns-prefetch" href="//www.baidu.com" />
复制代码

看一下各平台的处理

  • 京东
<!-- 太多了,随便copy俩 -->
<link rel="dns-prefetch" href="//static.360buyimg.com">
<link rel="dns-prefetch" href="//misc.360buyimg.com">
...
...
复制代码
  • 百度
<link rel="dns-prefetch" href="//dss0.bdstatic.com">
<link rel="dns-prefetch" href="//dss1.bdstatic.com">
...
...
复制代码

不多举例了,可以找一些大型网站看一下

Http

tcp握手和挥手是为了确认保证客户端和服务端接收、发送能力,以及数据传输的安全和完整性,这里一笔带过

前端资源包括图片、icon、js、css等均是通过http请求获取的,如果能够加快资源的获取速度可以极大的提高页面性能,注意:这里说的获取而不是下载

限制条件

我们获取资源速度的时间有以下条件:

  • tcp连接频率
  • 文件获取方式,缓存or下载
  • 下载文件大小
  • 下载数量

根据上述限制条件,接下来我们一一分析

TCP连接频率

说到tcp链接就要聊到各http版本的链接方式

http1.0

http1.0规定客户端服务端允许建立短暂链接,每次请求需要建立新的链接,服务端处理完毕断开链接,当页面中资源多起来的时候,频繁的链接对服务端是极大的消耗,而且对客户端获取到资源的时间也会正常,一般情况下浏览器对同一个域名的请求上线是6-8个,超过这一数量可能会阻塞,即线头阻塞(HOLB: head of line blocking)

http1.1

http1.1做了一些优化:

  • 默认开启keep-alive

http1.1支持长连接,默认开启Connection: keep-alive,不会关闭tcp连接,可以继续发送http请求,一定程度上弥补了频繁链接的问题,但长连接不能并行发起请求,各请求依赖上一个请求的响应,所以有时候依然会造成阻塞

  • 管线化(pipelining)

http1.1管线化(pipelining)支持在一个tcp连接中多个http请求一一发送,各请求不需要等待服务器对前一个请求的响应,不过客户端在接收响应的时候还是按照发送的顺序接收的,如果前一个请求阻塞后续的请求都需要等待,所以仍会造成阻塞

http2.0

http2.0是基于SPDY设计的,主要有一下特点:

  • 多路复用

即共享tcp连接,一个request请求对应一个id,一个连接上可以有多个request请求,每个连接的request可以随机混杂在一块,接收方可以根据requestidrequest在归属到各自不同的服务器里

  • 新的二进制格式

http1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑http2.0的协议解析决定采用二进制格式,实现方便且健壮

  • header压缩

http1.x中header携带了很多重复复杂的信息,http2.0使用算法压缩了header,减小包的大小和数量

  • 服务端推送

采用了SPDY网页,如服务器向客户端推送style.css的同时会推送一个style.js,当客户端再次获取style.js时会从缓存中获取,不需要在发请求

相对于1.x版本,http2.0请求资源的速度会快很多

总结

so,使用http的优先级如下:http2.0 > http1.1 > http1.0

文件获取方式

毫无疑问从缓存中拿资源的效率要高于下载,这里的缓存指的是http缓存,而http缓存又分为强缓存协商缓存简单聊一下它们的区别:

第一次请求到资源后,浏览器会根据请求头中的缓存标识决定是否缓存、如何缓存资源,强缓存和协商缓存都是拉取的缓存数据(协商失败则是下载新资源),区别就在于是否和服务器有交流

强缓存

浏览器请求资源时会先获取缓存的请求头信息,如果Cache-Controlexpires命中强缓存则拉取本地缓存

expires

expires是http1.0的规范,它的值是一个绝对时间的GMT格式的时间字符串(如Wen, 18 Mar 2020 17:25:00 GMT),这个值表示资源的过期时间,未超过该时间则会从本地拉取缓存

缺点是expires的值是绝对时间,这对客户端和服务端时间一致性要求就比较高,一旦修改了客户端时间或客户端和服务端时间差异较大就会出现缓存混乱的问题

Cache-Control

Cache-Control是http1.1的规范,通过max-age来判断(如max-age=1000,表示1秒后过期),这是一个相对时间,除了max-age还有以下属性:

  1. no-cache: 不使用强缓存,但可以使用协商缓存
  2. no-store: 强缓存和协商缓存均无法使用
  3. pubilc: 浏览器和代理服务器都可以对资源进行缓存
  4. privite: 只有浏览器可以缓存,代理服务器不能缓存
  5. s-maxage: 代理服务器的缓存有效期,同时设置max-age和s-maxage,客户端会使用max-age,代理服务器会使用s-maxage

其实还有一个Pragma,不过它已经逐渐被抛弃了,这里不做过多了解了

优先级

Cache-Control > expires

协商缓存

当强缓存没有命中,接下来就要看是否能够命中协商缓存,所谓协商就是浏览器与服务器协商,如果资源还是老的资源没有更新变动则返回304告诉浏览器从缓存中拉取数据,主要是通过请求头中的last-modifiedetag来判断是否需要重新拉取数据

last-modified/if-modified-since

如果第一次请求服务器返回的头信息带有last-modified信息后续请求会携带if-modified-sincelast-modified记录的是资源的最后修改时间,if-modified-since记录的是上次last-modified,服务器会将浏览器传过来的if-modified-since和资源修改时间做对比,如果时间一致则表示资源未修改返回304,拉取缓存的资源,如果时间不一致则向服务器请求新的资源,更新last-modified为新的修改时间

缺点是只能精确定秒,秒以内修改无法,假如一个文件1s内修改了n次,last-modified是无法捕获的。另外一个问题是只要文件被修改了,无论内容是否有变化,都会以最新的修改时间为判断依据,这就导致了一些没必要的请求,接下来的etag就是来解决这个问题的

etag/if-none-match

last-modified相同,第一次请求服务器返回的头信息携带了etag后续的请求会携带if-none-matchif-none-match记录的是上次的eatgetaglast-modified判断同样过程相似,服务器对比浏览器传过来的if-none-match和当前内容的标识字符串,不同则返回新的资源和新的标识字符串

last-modified区别:
  1. etag是唯一标识字符串,只要内容变动etag就会变化,更精确的感知内容的变化
  2. 即使304,由于etag重新生成过,服务器还是会将etag返回,即使这个etag没有变化
优先级

etag > last-modified

下载资源

即使是下载新资源也有相应的优化方案

link
  • preload

preload 提供了一种声明式的命令,让浏览器提前加载指定资源(加载后并不执行),需要执行时再执行,使用如下:

<!-- 需要使用as属性指定特定的资源类型以便浏览器为其分配一定的优先级 -->
<link href="pre.css" rel="preload" as="style">
复制代码

不要随意使用preload,一旦使用preload无论资源是否被使用都会被提前加载,会给网页带来不必要的负担

  • prefetch

它的作用是告诉浏览器加载下一页面可能会用到的资源,可以用来优化下一页面的加载速度,使用如下:

<link href="pre.css" rel="prefetch">
复制代码

preloadprefetch同样适用于js:

<link href="pre.js" rel="preload" as="script">
<link href="pre.js" rel="prefetch">
复制代码
script

JS可能会修改DOM或CSS,所以浏览器遇到 script 标记,会唤醒 JavaScript解析器,然后就停止解析HTML,所以日常开发通常会把script标签放在最下边,通常遇到script标签就会去加载资源,我们可以通过deferasync异步加载

  • defer

js脚本在文档加载解析完毕DOMContentLoaded执行之前完成

  • async

加载和渲染后续文档元素的过程与js脚本并行进行,有很多的不确定性,可能在文档解析完毕之前也可能之后

defer更符合我们日常的需求,保险起见建议还是要把script标签放在最后

文件大小

减小文件大小同尺寸有如下操作:

  1. 压缩图片、css、js等资源
  2. 前端打包gzip,服务端开启gzip模式
  3. 拆包,将css资源抽离,同时方便缓存
  4. 抽离公共模块,同样也可以方便缓存
  5. webpack4+生成环境默认开启tree-shakescope模式,这些都依赖import的引入
  6. 生产关闭source-map,但当你需要监听生产环境问题是需要自行打包一份source-map
  7. 代码优化(脑补中)

下载数量

同一域名下载数量有限,所以我们通常会尽量减少文件的数量,通常有如下操作:

  1. 小图片转base64(webpack配置),或使用icon代替图片,或者使用雪碧图
  2. 提供静态资源服务器存储静态资源或cdn
  3. 动态加载,通过import即可实现
  4. deferasync同样可以减少同一时间下载文件数量

wbepack

上班已经涉及到webpack的一些内容,这里介绍一下上述没有提到的内容

hash

正确处理webpack的不同hash值,配合后续缓存策略,简单介绍一个3个hash

  • hash

整体项目有变化就会变化

  • chunkhash

根据不同的入口文件解析、构建,生成哈希值,将一些共用模块和逻辑抽出,则不会受业务逻辑的变化影响

  • contenthash

当前文件内容变化才会变化,所以通常将css抽离出js,加上contenthash,即使js变化了,只要css没变则不会发生变化

多进程

  • 编译多进程

Happypack可以开启多进程编译,加快打包编译速度

  • 压缩多进程

UglifyjsWebpackPlugin压缩是单进程,可以使用TerserWebpackPlugin代替

公共资源抽离

你可以通过dllPluginexternals进行静态依赖包的分离,当然我认为通过externals方式并通过cdn方式更简单、更有效一些

预渲染、seo

可以通过prerender-spa-plugin去做一些与渲染、seo的优化,又或者是骨架屏

结语

前端资源都可以算是静态资源,可以做的优化也非常多,实际情况又能落地多少既要看项目合不合适这些优化方案,也要看是否有精力去做这些优化,可以确定的是一旦形成习惯,一切水到渠成

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享