not a better man

前端技术

html5优化指南

web前端发展到2018年的现在,技术已日新月异,从以前的table布局,到利用position + float+盒模型布局flex弹性布局,到grid布局,前端技术从JqueryMVVM,到Google提出的PWA,以及现在的webassembly 人类对于更好的体验,更快的速度的追求,是无止境的,前端页面优化一直是个永恒的话题。那么到现在,我们针对前端的优化有哪些方案呢?

页面加载速度

页面加载速度慢是造成用户体验差的最致命的因素,这个是我们首先需要考虑的一个因素。
从下面的研究数据我们可以看出一些端倪

  • 53%的用户会放弃一个加载超过3s网站 —SOASTA Google study report
  • 当一个网站的加载变快,用户停留的时间就越长的,用户更愿意在加载快的网站上购买东西。 —WPO Stats
  • 加载速度慢的网站,对SEO优化是有害的,这个会降低网站的排名,导致网站访问的频率降低。 —Search Engine Land

我摘取WPO Stats中的几条条数据

 

页面速度加载提高,用户的访问量也相应提高。我们现在不考虑服务端的一些因素导致页面加载变慢的问题。那么页面加载速度一个原因是网络延迟,另一个是网络带宽

网络延迟

网络延迟是不可避免的,因为光速是有上限的,此外网络延迟与网络带宽也有关系,
我们先避开网络带宽这个因素,如果把·所有的资源放在一个地点的服务器上,那么对于距离远的用户来说,由于光速的限制,这个延迟无法避免,针对这种情况,我们怎么办,首先的问题就是解决距离的问题导致的网络延迟

CDN

我们提出了CDN服务的概念,让用户访问的资源离用户更近一点,这样能够让光速的限制减少,此外也减少服务端的压力,那么CDN服务各个数据同步的问题,这个问题我们不需要去考虑。

网络连接的问题

从协议层考虑

tcp协议的缺点

我们现在网络协议分为五层协议,从物理层数据链路层IP层传输层,应用层 对每一层协议都能做到对应的优化吗?从目前情况看,优化数据链路层IP层(IPv6目前张正在推广,相对于ipv4的优点,本文不论述) 是很难做到的,这样涉及的网路基础设施的问题,现在web服务,我们传输层走的协议主要是TCP,TCP协议的优点反而是网络延迟的一个大问题我们为了能够在不稳定的信道上进行稳定的``双向通信,产生了伟大的TCP协议。但是TCP建立一个双向连接,就需要三次握手。如下图所示

这个是一个比较棘手的问题,那么能不能减少到一次呢?,此外TCP有一个队首阻塞的问题,当前面的包丢失之后,不管后面的包是否与前面的包是无关的,一定要等到前面的包重新接受之后才能够发送给上层服务。针对TCP协议设计的这个问题。我们可以从UDP协议入手,Google在此基础上定义的一种新的协议QUIC 。该协议能够保证双向稳定的东西,但是建立连接只需要一次握手。这在移动端通信很有优势。

http2协议的优势

那么HTTP协议是否有问题呢,现在常用的协议是http1.1,不过很多网站都开始使用http2协议了。http2协议有什么优势呢,说http2的优势,那么我们需要看一看http1.1协议的缺陷。这样比对才能够发现其优点。

http1.1协议的不足之处

http1.1虽然改进了缓存机制,来减少资源的请求次数增加了持久化连接来支持连接重用,持久 HTTP 可以让我们重用已有的连接来完成多次应用请求,减少建立新的tcp连接需要的握手往返的时间,但多次请求必须严格 满足先进先出(FIFO)的队列顺序:发送请求,等待响应完成,再发送客户端队列 中的下一个请求,如下图所示

 
从图中我们充分利用一个tcp连接来传输一个html页面 一个css样式表,这样我们能够充分利用tcp连接来传输各种资源,但是,要是我服务器能够并行处理管道中的资源,能够减少先进先出导致多出56ms的请求,由于http1.1协议的局限性,只能严格串行的返回响应,并且不允许一个连接上的多个响应数据交错到达(多路复用),一个响应必须完全返回之后,下个响应才会开始传输。如果并行传输的话,是下图情况

并行传输的话,一个请求与响应的往返时间就可以节省了,这样在传输过程中就节省了58ms。http1.1针对这种请求提出了管道化的概念,但是并不支持,相互矛盾。而只支持先进先出,导致如果html页面丢失的包,css页面请求就会一直挂起。针对这种情况,我们打算建立多个tcp连接,来并行加载各种资源。

http2

http2协议完善了http1.1的管道化未能完成的任务,完善多路复用的概念,并对http协议的报头进行了压缩,减少传输的体积。这样就能够充分利用一个tcp连接来并行的传输资源。
上面我们去利用新的协议解决旧协议的缺陷,来减少网络延迟,

提前解析,提前加载

此外还有其他的方法吗。
那么我们从打开一个网站到呈现一个网页,需要的过程是,解析DNS –建立tcp连接发送请求收到资源浏览器去显示页面
那么在DNS解析这一块,我们能不能去提前解析我们之后要用到的资源,这样就减少域名的解析的时间。
我们可以看看京东首页的页面

上面添加了很多link标签 rel的值是dns-prefetch 这个能够提前解析要用到DNS
此外还有preconnect ,preload,prefetch,preconnect 这样的一切一切都是为了提高页面加载速度

网络带宽

我们网络的带宽的能承受的信息不是无限的,带宽是一定的,加载的资源越少,加载速度越快,网络延迟降低。

压缩资源体积

在网络传输上,对图片js ,css等资源信息进行GZIP,这个能够减少传输体积,浏览器接受之后再进行解压。

图片适配

我们现在面临的设配有PC端,Ipad端,手机等其他显示设备,每台设备PPI并不一样,如果简单粗暴的在各种设配上使用同样的高分辨率的图,那对于分辨率低的设备来说,高分辨率图是一种浪费,我们希望针对不同分辨率的设备加载不同分辨率的图片
对于<img>标签,我们可以使用srcset 和sizes属性来适配不同设备

<img srcset="elva-fairy-320w.jpg 320w,
             elva-fairy-480w.jpg 480w,
             elva-fairy-800w.jpg 800w"
     sizes="(max-width: 320px) 280px,
            (max-width: 480px) 440px,
            800px"
     src="elva-fairy-800w.jpg" alt="Elva dressed as a fairy">

针对相同尺寸,不同分辨率,可以这样设置

<img srcset="elva-fairy-320w.jpg,
             elva-fairy-480w.jpg 1.5x,
             elva-fairy-640w.jpg 2x"
     src="elva-fairy-640w.jpg" 
     alt="Elva dressed as a fairy">

这样能够节省带宽。不同浏览器支持的情况如下

此外我还可以使用<picture>标签

<picture>
  <source media="(max-width: 799px)" srcset="elva-480w-close-portrait.jpg">
  <source media="(min-width: 800px)" srcset="elva-800w.jpg">
  <img src="elva-800w.jpg" alt="Chris standing up holding his daughter Elva">
</picture>

那对于相同尺寸,不同分辨率的设备 显示不同分辨率的的背景图片,该怎么操作呢?我们可以使用image-set样式属性,但是目前的支持度不够

div {
    background-image: image-set( "test.png" 1x, "test-2x.png" 2x, "test-print.png" 600dpi );
}

目前的支持度如下图,chrome浏览器 还需要添加-webkit-前缀

图片类型

我们的web服务充斥着大量的图片资源,我们现在主要用到的图片类型主要是png,gif,jpeg每种图片都有自己的优势,如jpeg格式的图片,可以设置0-10等级,等级越低,图片体积约少,图片质量越低,我们可以根据需求选择不同质量的图片,此外我们能不能寻找更优的压缩算法,能够在相同字节的体积上,显示的图片质量更好,Google是无孔不入,提出了webp格式的图片,比jpegg更优的压缩算法,这样我们也能够节省带宽。但是并不是所有浏览器都支持该格式的图片

自定义字体

有时我们需要实现特殊的显示,如特殊的文字,特殊的logo,在以前我们是用图片去显示,但是我们可以自定义字体,用在我们网站上。利用@font-face 来加载我们需要的字体库,有时候比图片显示相同效果,更加节省带宽。
为了提高页面加载速度,我们想尽各种办法,从协议层去考虑,从网络带宽上去考虑,尽量去优化各个节点,优化的脚步永不停止,那么除了以上的优化手段,还有其他手段吗

加载目前需要的资源

现在我们很多web app 都是单页应用,在3年前,大部分是一次加载所有脚本和css,现在有了webpack等构建工具, 我们可以实现懒加载可以针对路由加载不同的组件,随着dynamic import()支持的浏览器越来越多,我们可以更加精细化加载不同的js脚本,节省带宽。dymaic import支持度如下

缓存

我们希望我们加载过的资源再次访问的时候,如果没有更新,能够不需要去服务端拉取对应资源,这对于带宽的节省能够起到相当大的作用,能够显著的减少网络延迟。
针对缓存这块,http协议做了很多规范,我们希望缓存那些不经常变动的资源,我们利用webpack去做到对于常用的库单独打包,进行md5散列,部署到cdn上,配合http etag

HTTP2 push

我们当解析index.html文件的是,解析index.html ,当我们碰到link script img 这些标签,我们再次发送http请求,这样其实会浪费时间,但是http2 支持服务端推送,当我们设置好我们要推送的资源的时候,在请求index.html的时候,会主动发送css 文件,js文件,这样会发送减少http请求需要的时间,但是服务端推送怎么去处理缓存的边界,怎么去处理过度推送,假如我们在请求页面A的时候,页面A上的缓存资源B,C,D,E都有了,但是服务端配置了页面A推送B,C,D 这样会造成不必要的推送。就是在推送的过程中,怎么去解决不必要的推送,这是一个需要考虑的问题,所以针对页面的优化,有时候并不是过度优化是合适的

页面资源监控

我们对页面加载的性能做了优化,那么部署到服务上去,我们怎么去精确的到每一个用户页面加载性能

  • dns解析时间
  • 首字节到达时间
  • scirpt脚本加载时间
  • 样式表加载时间
  • 首次绘图时间
  • 首次内容绘图时间

现在浏览器支持了performance 我们知道可以从performace.getEntries()方法知道不同资源加载的时间,包括css,javascript,img,等等,也包括xmlhttprequest请求后端接口的开始时间,结束时间,如下图所示

我们可以通过
performance.getEntriesByType("navigation") 

查看域名解析,domCompletefetchStart等等消耗的时间。查看哪一步导致速度变慢,然后一步步去优化性能。

从上图可以看出,几乎每一个环节都有优化的可能性。
移动网络的优化,从2G3G4G已经要到的5G,都是在解决网络延时,与网络带宽的问题。
到这个时候,我们现在基本做到了页面资源下载的时候优化,这个时候就是代码层面的优化

代码层面的优化

页面程序呈现出来,大致分为上面三个阶段代码层面的优化 占据执行阶段渲染阶段,但是我们前端的资源其实是有优先级,那么我们在代码层面告诉浏览器,哪些资源是重要的,哪些资源是可以滞后的,更加精细的控制颗粒度。

异步图像

我们图像的解析是会阻塞脚本的解析,编译,执行的,但是有时候,图片对我们来说,并不是那么重要,我们可以使用async属性来 异步加载图像

web workers 与shareArrayBuff

DOMChangeList

目前只是一个提议,这个提议见https://github.com/whatwg/dom/issues/270

发表评论