会强化,我就并非不优,是看著慢,哎~,是玩玩~。
序言
操控性强化是两个工程项目产业发展到很大末期后绕不开的热门话题,也是每一技师内心深处总有一天在企盼的刺。
归纳呵呵常见的后端操控性强化的方式,期望对我们很多协助~。
操控性可能将增添的负面影响(售卖恐惧警示❗️)
换言之当你做的酷炫特技即使慢了0.1秒就少被两个人看见时的伤感(ㄒoㄒ),
换言之当你引以为豪的技术细节可视化即使慢了0.2秒就被竞争者的愤世嫉俗交互丢下使用者的恼怒(╯>д<)╯⁽˙³˙⁾,
换言之当你精心设计打造出的很漂亮网页即使慢了0.3秒就被浏览器恶毒的排在前面的无可奈何∑(O_O;)。
因此,是这时候严打了,孙积孚操控性的光辉,之者责无旁贷( ̄▽ ̄)/。
增容辅助工具
Network液晶
Network液晶历史记录了与伺服器可视化的具体内容技术细节。
在这里我们可以看见发起的请求数量,传输体积以及解压缩后的体积,同时还可以知道哪些资源是命中了强缓存,哪些资源命中的协商缓存。
查看某两个请求的瀑布流可以让我们清晰的看见两个资源从伺服器到达我们的电脑所花的时间。
如上图,排队用了1.65ms,DNS查询用了21.47ms,initial connection(进行TCP握手的时间)用了56.25ms,SSL握手的时间用了37.87ms,然后又用了100多ms第两个字节到达我们的电脑(TTFB – 上面的查询/建立),接收整个文档花了17ms。
这这时候我们基于上面的信息就可以粗略的得到,如果能在请求资源之前如果已经得到DNS地址(预查询)可以省去21ms,已经进行过握手可以省去100ms(预连接),如果干脆请求也不请求可以省去200ms(缓存)继而针对这些点做对应的策略。
Network液晶可以让我们初步评估网站操控性,对网站整体的体积,网络的负面影响增添两个整体的认知,同时提供一些辅助功能,如禁用缓存,block某些资源。
lighthouse液晶
lighthouse是对网站整体的评估,通过几个不同的指标给网站进行打分。
First Contentful Paint 首屏渲染时间,Chrome会取第两个渲染出来的元素作为时间参考。
Time to Interactive 可可视化时间,从能看见能摸的时间点。
Speed Index 速度指数,网页的填充速度。
Total Blocking Time 从能看见能摸之间超过50ms的任务总和。
Largest Contentful Paint 网页中最大的那块渲染的时间点。
Cumulative Layout Shift 元素移动所累积的时间点,比如有两个absolute的元素突然从左边移到了右边。
同时针对网站的信息,lighthouse还会给出一些完善建议:
这些建议可以协助我们在接下来的强化中提供两个大致的方向。
performance液晶
performance液晶会给我们提供两个具体内容的执行过程,从HTML文档下载,解析HTML,到解析CSS,计算样式,执行JS。
火焰图
从火焰图我们可以找到长任务,分析长任务,或者找到某些无关紧要的任务把他们拆分,延后,强化使他们达到两个理想状态。
performance monitor液晶
performance monitor让我们监控内存和CPU的占用,它给出的是整体的占用数据,可以用来观察某一段代码某两个特技会不会造成操控性负面影响。
webpack-bundle-analyze
如果你用到了webpack打包,可以用它来分析打包后的文件,做成具体内容策略。
从输入两个URL谈起
这是两个URL为了见到你穿越无数路由器的感人故事。
DNS查询
与伺服器可视化首先要进行DNS查询,得到伺服器的IP地址,浏览器会首先查询自己的缓存,后会查询本地HOSTS,如果仍然没找到会发起向DNS伺服器查询的请求。
在这里我们可以做的强化不多,DNS是我们相对不可控的两个条件,但我们仍然可以做的两个强化策略是预查询。
进行DNS预查询
在文档顶部我们可以将我们即将要请求的地址的DNS预先查询,通过插入两个link标签
<link rel=”dns-prefetch” href=”https://fonts.googleapis.com/”>
来告知浏览器我们将要从这个地址(通常会是存放静态资源的CDN的地址,)拉取数据了,你先查询呵呵,当用到的这时候就可以直接拿到对应的IP。
建立HTTP(TCP)连接
得到伺服器IP后,首先进行三次握手,后会进行SSL握手(HTTPS),SSL握手时会向伺服器端确认HTTP的版本。
针对这方面的强化,后端可做的事情不多,主要是伺服器端的事情,不过仍然要介绍呵呵后端可以看得到的策略。
keep-alive
由于TCP的可靠性,每条独立的TCP连接都会进行一次三次握手,从上面的Network的分析中可以得到握手往往会消耗大部分时间,真正的数据传输反而会少一些(当然取决于内容多少)。HTTP1.0和HTTP1.1为介绍决这个问题在header中加入了Connection: Keep-Alive,keep-alive的连接会保持一段时间不断开,后续的请求都会复用这一条TCP,不过由于管道化的原因也会发生队头阻塞的问题。
HTTP1.1默认开启Keep-Alive,HTTP1.0可能将现在不多见了,如果你还在用,可以升级呵呵版本,或者带上这个header。
HTTP2
HTTP2相对于HTTP1.1的两个主要升级是多路复用,多路复用通过更小的二进制帧构成多条数据流,交错的请求和响应可以并行传输而不被阻塞,这样就解决了HTTP1.1时复用会产生的队头阻塞的问题,同时HTTP2有首部压缩的功能,如果两个请求首部(headers)相同,那么会省去这一部分,只传输不同的首部字段,进一步减少请求的体积。
Nginx开启HTTP2的方式特别容易,只需要加一句http2既可开启:
server { listen 443 ssl http2; # 加一句 http2. server_name domain.com; } 复制代码成本低廉,效果巨大。
缓存
入时身心愉悦,如果两个资源完全走了本地缓存,那么就可以节省下整个与伺服器可视化的时间,如果整个网站的内容都被缓存在本地,那即使离线也可以继续访问(很酷,但还没有完全很酷)。
HTTP缓存主要分为两种,一种是强缓存,另一种是协商缓存,都通过Headers控制。
整体流程如下:
强缓存
强缓存根据请求头的Expires和Cache-Control判断是否命中强缓存,命中强缓存的资源直接从本地加载,不会发起任何网络请求。
Cache-Control的值有很多:
Cache-Control: max-age=<seconds> Cache-Control: max-stale[=<seconds>] Cache-Control:min–fresh=<seconds> Cache-control: no–cache Cache-control: no–store Cache-control: no-transform Cache-control:only–if-cached 复制代码常见的有max-age,no-cache和no-store。
max-age 是资源从响应开始计时的最大新鲜时间,一般响应中还会出现age标明这个资源当前的新鲜程度。
no-cache 会让浏览器缓存这个文件到本地但是不用,Network中disable-cache勾中的话就会在请求时带上这个haader,会在下一次新鲜度验证通过后使用这个缓存。
no-store 会完全放弃缓存这个文件。
伺服器响应时的Cache-Control略有不同,其中有两个需要注意下:
public, public 表明这个请求可以被任何对象缓存,代理/CDN等中间商。private,private 表明这个请求只能被终端缓存,不允许代理或者CDN等中间商缓存。Expires是两个具体内容的日期,到了那个日期就会让这个缓存失活,优先级较低,存在max-age的情况下会被忽略,和本地时间绑定,修改本地时间可以绕过。
另外,如果你的伺服器的返回内容中不存在Expires,Cache-Control: max-age,或 Cache-Control:s-maxage但是存在Last-Modified时,那么浏览器默认会采用两个启发式的算法,即启发式缓存。通常会取响应头的Date_value – Last-Modified_value值的10%作为缓存时间,后浏览器仍然会按强缓存来对待这个资源一段时间,如果你不想要缓存的话务必确保有no-cache或no-store在响应头中。
协商缓存
协商缓存一般会在强缓存新鲜度过期后发起,向伺服器确认是否需要更新本地的缓存文件,如果不需要更新,伺服器会返回304否则会重新返回整个文件。
伺服器响应中会携带ETag和Last-Modified,Last-Modified 表示本地文件最后修改日期,浏览器会在request header加上If-Modified-Since(上次返回的Last-Modified的值),询问伺服器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来。
但是如果在本地打开缓存文件,就会造成Last-Modified被修改,因此在HTTP / 1.1 出现了ETag。
Etag就像两个指纹,资源变化都会导致ETag变化,跟最后修改时间没有关系,ETag可以保证每两个资源是唯一的
If-None-Match的header会将上次返回的ETag发送给伺服器,询问该资源的ETag是否有更新,有变动就会发送新的资源回来
ETag(If-None-Match)的优先级高于Last-Modified(If-Modified-Since),优先使用ETag进行确认。
协商缓存比强缓存稍慢,即使还是会发送请求到伺服器进行确认。
CDN
CDN会把源站的资源缓存到CDN伺服器,当使用者访问的这时候就会从最近的CDN伺服器拿取资源而并非从源站拿取,这样做的好处是分散了压力,同时也会提升返回访问速度和稳定性。
压缩
合理的压缩资源可以有效减少传输体积,减少传输体积的结果是使用者更快的拿到资源开始解析。
压缩在各个阶段都会出现,比如上面提到的HTTP2的首部压缩,进行到这一步的压缩是指对整个资源文件进行的压缩。
浏览器在发起请求时会在headers中携带accept-encoding: gzip, deflate, br,告知伺服器客户端可以接受的压缩算法,后响应资源会在响应头中携带content-encoding: gzip告知本文件的压缩算法。
GZIP压缩
GZIP是非常常见的压缩算法,现代客户端都会支持,你可以在上传文件时就上传一份压缩后的文件,也可以让Nginx动态压缩[4]。
进行网页渲染
关键渲染路径
关键渲染路径是浏览器将HTML/CSS/JS转换为屏幕上看见的像素内容所经过的一系列步骤。
浏览器得到HTML后会开始解析DOM树,CSS资源的下载不会阻塞解析DOM,但是也要注意,如果CSS未下载解析完成是会阻塞最终渲染的。
从Performance液晶中可以清晰的看见浏览器如何解析HTML的:
得到HTML后首先会解析HTML,然后解析样式,计算样式,绘制图层等等操作,JS脚本运行,后可能将会重复这一步骤。
在这里后端可以做的事情多了起来,接下来自顶向下说起。
预加载/预连接内容
和前面说的DNS预查询一样,可以将即将要用到的资源或者即将要握手的地址提前告知浏览器让浏览器利用还在解析HTML计算样式的时间去提前准备好。
preload
使用link的preload属性预加载两个资源。
<link rel=“preload”href=“style.css” as=“style”> 复制代码as属性可以指定预加载的类型,除了style还支持很多类型,常见的一般是style和script,css和js。
其他的类型可以查看文档
prefetch
当然对当前网页的要用preload,不要用prefetch,可以用到的两个场景是在使用者鼠标移入a标签时进行两个prefetch。
preconnect
preconnect和dns-prefetch做的事情类似,提前进行TCP,SSL握手,省去这一部分时间,基于HTTP1.1(keep-alive)和HTTP2(多路复用)的特性,都会在同两个TCP链接内完成接下来的传输任务。
script加标记
当浏览器解析至script标签时,浏览器的主线程就会等待script,或者运行script,然后继续开始构建,在以前,如果你把script标签放到了文档的最上面,那么在等待下载和运行的这段时间内网页就会处于白屏和无法操作的状态,并且并非并行的下载,浏览器会逐个下载并运行,这是两个相当糟糕的体验。因此都会选择将script放在文档底部,尽可能将推后脚本的执行时机,不过并不完全可控。
时至今日,我们可以给script标签增加标记,使其异步(延迟)运行,把可控权交给开发者。
async标记
<script src=”main.js” async>
async标记告诉浏览器在等待js下载期间可以去干其他事,当js下载完成后会立即(尽快)执行,多条js可以并行下载。
async的好处是让多条js不会互相等待,下载期间浏览器会去干其他事(继续解析HTML等),异步下载,异步执行。
defer标记
<script src=”main.js” defer></script>
与async一样,defer标记告诉浏览器在等待js下载期间可以去干其他事,多条js可以并行下载,不过当js下载完成后不会立即执行,而是会等待解析完整个HTML后在开始执行,而且多条defer标记的js会按照顺序执行,
<script src=“main.js” defer></script> <script src=“main2.js” defer></script> 复制代码即使main2.js先于main.js下载完成也会等待main.js执行完后再执行。
到底该用哪个标记
两个标记都是为了让script标签实现异步下载,主要的区别在于async无法保证顺序且下载完就会执行而defer则会等待整个HTML解析后才会开始执行,并且按照插入的顺序执行。
如果两个script之间没有依赖关系并且可以尽快执行的更加适合使用async,反之如果两个script之间有依赖关系,或者期望优先解析HTML,则defer更加适合。
视窗外的内容懒加载
懒加载也是两个经常被提及的技术,视窗外的内容是不会被使用者立即看见的,这时加载过多的内容反而拖慢了网站整体的渲染,我们就可以用懒加载推迟这部分内容的加载来达到加速可访问和可可视化性的目的,等使用者即将到达视窗内的这时候再开始加载这部分内容,通常懒加载会与loading和骨架屏等技术搭配使用。
减少无意义的回流
回流与重绘是两个老生常谈的问题,当浏览器大小改变/滚动,DOM增删,元素尺寸或者位置发生改变时都会发生回流,回流意味着浏览器要重新计算当前网页的与之相关的所有元素,重新进行整体的布局。
这是两个非常消耗操控性的事情,很多情况下回流无法避免,很多情况下则可以省略无意义的回流,比如用Js将20个li更改到同样的尺寸时避免将每一li都即时更改,应该用class一次性更改。
图片视频选择合理的尺寸
分辨率越高的图片显示出来越消耗操控性,当然增添的好处是更加的清晰,但很多情况下清晰并并非两个特别重要的标准,我们可以牺牲一部分清晰度来让图片视频体积更小,通常PC使用1倍图,移动端使用2倍图就够了,原图可以结合懒加载等待空闲或者主动触发时在加载,像是微信QQ等聊天时发的表情包一样,都是点开才会加载原图。
这往往是两个容易被忽略(可能将即使感觉没必要)提升又很大的事情,如果你的网站图片很多强烈建议着手强化。
选择两个支持动态剪裁的云服务即可享受这份美好~。
写代码时可以做的事
上面从代码写完的角度谈起,接下来从写代码的角度谈起。
首先是打包。
Tree-shaking
Tree-shaking指的是消除没被引用的模块代码,减少代码体积大小,以提高网页的操控性,最初由rollup提出。
webpack2加入对Tree-shaking的支持,webpack4中Tree-shaking默认开启,Tree-shaking基于ESModule静态编译而成,因此如果想要生效,在写代码的这时候注意不要用CommonJS的模块,同时也要注意不要让babel给编译成CommonJS的形式。
Tree-shaking连带的有两个sideEffects的概念,即使Js的特性使得完全静态分析是两个很难的事情,很多代码往往会带有副作用,比如呵呵代码:
class Handler { handleEvent() { console.log(You called me.) } } window.addEventListener(visibilitychange, new Handler()) 复制代码在上面的代码中不存在任何显式的调用handleEvent,但当visibilitychange发生时Js会去调用handleEvent,这个类就属于有副作用的一种,它是不能被抖掉的代码(实际上webpack也不会对类有啥想法)。
如果你确定某个文件是这种含有副作用的文件,可以在package.json中添加sideEffects: [class.js]让webpack强行打包进去。
对于一些第三方库来说为了兼容性考虑通常入口文件都是CommonJS的形式,这时想要成功抖掉不需要的部分通常有两种方式。
以出镜率极高的lodash为例。
lodash默认是CommonJS的形式,使用常规的方式import { cloneDeep } from lodash导入后,webpack会把整个lodash打包进来,这对于只用到了两个函数的我们的来说显然不可接受,此时可以改写为:
import cloneDeep from lodash/cloneDeep 复制代码或者如果提供了ESModule的版本也可以直接使用:
import { cloneDeep } from lodash-es 复制代码前者是精准导入不依赖re-exports,后者则是两个正经的Tree-shaking。
压缩
生产环境的代码并非给人看的,因此不需要考虑可读性(降低可读性还能提高被破解的成本o(≧口≦)o),尽可能将少的字符是最优选项,webpack4+无需配置默认会压缩代码,如果你想亲自试试,Js可选UglifyJS[8],CSS可选mini-css-extract-plugin[9]。
const MiniCssExtractPlugin = require(mini-css-extract-plugin); constCssMinimizerPlugin =require(css-minimizer-webpack-plugin); module.exports = { plugins: [ newMiniCssExtractPlugin({ filename:[name].css, chunkFilename: [id].css, }), ], module: { rules: [ { test:/\.css$/, use: [MiniCssExtractPlugin.loader, css-loader], }, ], }, optimization: { minimizer: [new CssMinimizerPlugin(), ], }, }; 复制代码使用动态import()代替静态import做条件渲染的懒加载
又是你~,懒加载。
如果你是Vue选手,最先接触到的import()可能将是vue-router文档中关于路由懒加载[10]的部分,其实具体内容到组件内部,也可以用同样的方式将一些基于判断条件的子组件/第三方库通过import()的方式导入,这样webpack在打包时会单独将它列为两个块,当符合判断条件时才会尝试去加载这个文件。
<template> <div> <sub-component v-if=“status” /> </div> </tamplate> <script> export default { components: { “sub-component”: () => import(./sub-component) // 感谢imluch 指正~ }, data() { return { status: false} }, mounted() { setTimeout(() => { this.status = true }, 10000) } } </script> 复制代码SSR
利用伺服器端优先渲染出某一部分重要的内容,让其他内容懒加载,这样到达浏览器端时一部分HTML已经存在,网页上就可以呈现出很大的内容,这里注意伺服器端渲染出来的HTML部分最好不要超过14kb,TCP慢开始的规则让第两个TCP包的大小是14kb,这是与网站可视化会接受到的第两个包。
更多强化方式
上面是目前所知的强化方式~,更多的强化待发掘中,路过的大哥们、小姐姐们还请给个赞,有问题的也可留言交流~。
原文来自:
https://juejin.cn/post/6966857691381645325@后端瓶子君