性能优化之从URL到页面展示

本文发于微信公众帐号:  
一界码农(The_hard_the_luckier)  
无需授权即可转载;
甚至无需保留以上版权声明…

今天从性能优化的角度再来看看从URL到页面展示,前两篇是从URL到页面展示的流程说起,中间过程发生了什么,并没有突出性能优化点,当然若不知其中间发生了什么不知其原理又谈何性能优化,所以若是还没看过之前的两篇笔记,可以先结合之前两篇一起看。

《从输入URL到页面展示,这中间发生了什么?》
《浏览器页面的渲染流程》

为了整理这一篇文章,特意在掘金上购买了《前端性能优化原理与实践》小册(ps: 要有输入才有输出哈),里面主要是“从输入 URL 到页面加载完成,发生了什么?”作为引子开启话题,这个面试题从大处着手思考,就是两个重要知识维度,一是网络层面,二是渲染层面。往细处说前者牵涉到DNS(域名解析系统)、IP寻址、TCP连接、http请求与响应等;后者则是进程与线程概念、DOM树、层叠样式、重排与重绘及合成等。
小册里面有一张性能优化的思维导图贴出来分享给大家

网络层面性能优化

关于网络层面优化,映入眼帘应该是资源请求与加载,至于DNS域名解析、IP寻址、TCP连接这种网络基础设施我们前端领域也做不了任何优化,而关于资源请求与加载的优化,就有很多方面着手,比如源头上代码打包压缩、构建优化,就要连同webpack工程化去做相关方面优化工作了。在webpack 的优化方面主要体现两个方面:

  • webpack 的构建过程太花时间
  • webpack 打包的结果体积太大

webpack常见的优化方案

  • 利用DllPlugin构建常用的依赖库
  • 使用Happypack将 loader 由单进程转为多进程
  • Tree-Shaking摇树功能在打包时提前去除无用代码
  • 利用缓存加速二次构建速度
  • 按需加载
    核心在于require.ensure(dependencies, callback, chunkName)
  • 利用Gzip压缩
    在我们请求资源头里request headers加上一句:accept-encoding: gzip

相信还有其他的webpack优化方案,欢迎各位补充哦~

图片的优化

我们常说页面优化要从关键资源入手,其实忽略了页面优化关键在于图片的优化。不知道大家认不认同这一观点,可能对于做电商来说非常认同,因为做电商本质上就是做图片;但不管怎样,图片的优化肯定是我们前端领域性能优化重要一环。想想我们工作中的图片优化,是不是在图片大小和质量上做“权衡”,所谓的优化相当于在做“权衡”,牺牲图片质量追求体验和性能。

我们熟悉下图片的几种格式:

  • JPEG/JPG 特点:有损压缩、体积小、加载快、不支持透明
  • PNG-8 与 PNG-24 特点:无损压缩、质量高、体积大、支持透明
  • SVG 特点:文本文件、体积小、不失真、兼容性好
  • Base64 特点:文本文件、依赖编码、小图标解决方案
  • WebP 特点:支持有损压缩和无损压缩,全能型选手

在工作中以上几种格式相信都用过,其实图片的优化还有很多有待挖掘,若不结合工作深入,很难有自己深刻的见解。好比性能优化并不好学,根本原因在于前端技术复杂又日新月异,知识不成体系,难以切入。

缓存优化

接着来说页面的非图片资源加载优化,也就是资源缓存优化,一来减少网络 IO 消耗,二来提高访问速度。浏览器缓存是一种操作简单、效果显著的前端性能优化手段。

浏览器缓存机制

览器缓存机制有四个方面,它们按照获取资源时请求的优先级依次排列如下:

1、Memory Cache;2、Service Worker Cache ;3、HTTP Cache ;4、Push Cache

  • MemoryCache
    指的是存在内存中的缓存。从优先级上来说,它是浏览器最先尝试去命中的一种缓存。从效率上来说,它是响应速度最快的一种缓存。

  • Service Worker
    是一种独立于主线程之外的 Javascript 线程。它脱离于浏览器窗体,因此无法直接访问 DOM。这样独立的个性使得 Service Worker 的“个人行为”无法干扰页面的性能,这个“幕后工作者”可以帮我们实现离线缓存、消息推送和网络代理等功能。我们借助 Service worker 实现的离线缓存就称为 Service Worker Cache。
    PS:大家注意 Server Worker 对协议是有要求的,必须以 https 协议为前提。

  • HTTP Cache
    是我们日常开发中最为熟悉的一种缓存机制。它又分为强缓存和协商缓存。优先级较高的是强缓存,在命中强缓存失败的情况下,才会走协商缓存。

    强缓存的实现:从 (http1.0) expires 到 (http1.1) cache-control
    协商缓存的实现:从 Last-Modified 到 Etag
    协商缓存机制下,浏览器需要向服务器去询问缓存的相关信息,进而判断是重新发起请求、下载完整的响应,还是从本地获取缓存的资源。

关于http缓存下面贴一张权威的流程图:

  • Push Cache
    指的是 HTTP2 在 server push 阶段存在的缓存。因为知识比较新工作上没有用过,不做过多笔记整理。
浏览器本地存储Web Storage

最后到了浏览器的本地存储数据了,在HTML5之前一直是cookie,那是为了存储会话session状态,后面随着技术发展有了localStorage和sessionStorage,以满足丰富的页面数据缓存需要。关于web Storage他们之间的区别在于生命周期作用域

  • 生命周期:Local Storage 是持久化的本地存储,存储在其中的数据是永远不会过期的,使其消失的唯一办法是手动删除;而 Session Storage 是临时性的本地存储,它是会话级别的存储,当会话结束(页面被关闭)时,存储内容也随之被释放。

  • 作用域:Local Storage、Session Storage 和 Cookie 都遵循同源策略。但 Session Storage 特别的一点在于,即便是相同域名下的两个页面,只要它们不在同一个浏览器窗口中打开,那么它们的 Session Storage 内容便无法共享。

除了耳熟能详的cookie、localStorage、sessionStorage,还有稀疏平常的浏览器数据库IndexDB,因为工作中没有用过,没有太多感触。不过感兴趣的还是可以了解一下,当然也需要有一个感性认识,万一后面数据复杂需要本地存储要用到浏览器数据库呢。
参考阮一峰的网络日志《浏览器数据库 IndexedDB 入门教程》

渲染层面的优化

梳理完了网络层面的优化,紧接着来看看渲染层面的优化部分。先回顾下浏览器渲染进程各个阶段的工作流程图

其中我们重点关注被HTML解释器解析的DOM、被CSS解释器解析的计算属性style、图层布局模块、图层绘制模块、视图合成模块。因为这些地方我们是可以做相关优化的,下面就从这5个方面一一梳理优化的点。

DOM的优化

对于DOM的优化并不陌生,主要在于减少DOM节点的嵌套深度、以及DOM节点的操作,以此避免渲染树的重排与重绘。

  • 重排:当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程也叫回流。
  • 重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式,会跳过重排环节。

CSS的优化

关于CSS的优化,主要体现在书写规范上面、CSS资源加载顺序上面、以及CSS动画上面。

CSS的样式规则

首先要知道CSS引擎查找样式表,对每条规则都按从右到左的顺序去匹配。知道这一知识点很重要,当我们在写样式的时候,就要避免使用通配符*,或者是元素标签,尽量使用选择器;另外要合理使用嵌套,不要多层深度嵌套(最高三层嵌套),尽可能使用类来关联每一个标签元素。

CSS的阻塞

根据上面的页面渲染流程图,我们知道CSS的加载阻塞是会影响到页面渲染的,也就是说:

CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。

对于CSS的阻塞优化,小册里面总结得很好,做到两点即可:一是尽早,将 CSS 放在 head 标签里;二是尽快,启用 CDN 实现静态资源加载速度的优化。

CSS的动画

CSS的动画方面优化主要是在合成环节上优化,例如使用transform动画会跳过渲染流程中的重排与重绘环节,直接进入合成阶段,因为transform属性是元素的既不布局也不绘制的属性。另外,合成相对于重排重绘来说,会大大提升绘制效率。

JS的优化

关于JS对于页面渲染的优化,主要也是阻塞和对DOM的操作,优化的点也在于减少重排和重绘。

JS的加载方式
  • 正常模式
    <script src="index.js"></script>
    一般我们会把js文件置于body结束标签的位置,是根据浏览器渲染原理来的,避免阻塞。
  • async 模式
    <script async src="index.js"></script>
    async 模式下,JS 不会阻塞浏览器做任何其它的事情。它的加载是异步的,当它加载结束,JS 脚本会立即执行。
  • defer 模式
    <script defer src="index.js"></script>
    defer 模式下,JS 的加载是异步的,执行是被推迟的。等整个文档解析完成、DOMContentLoaded 事件即将被触发时,被标记了 defer 的 JS 文件才会开始依次执行。
JS操作DOM优化

在JS中尽量减少 DOM 操作,避免过度渲染。比如:

let container = document.getElementById('container')let content = ''for(let count=0;count<10000;count++){ 
  // 先对内容进行操作
  content += '<span>我是一个小测试</span>'} 
// 内容处理好了,最后再触发DOM的更改container.innerHTML = content

另外可以用DocumentFragment给DOM分压,减少DOM的操作。

let container = document.getElementById('container')// 创建一个DOM Fragment对象作为容器let content = document.createDocumentFragment()for(let count=0;count<10000;count++){  // span此时可以通过DOM API去创建
  let oSpan = document.createElement("span")
  oSpan.innerHTML = '我是一个小测试'
  // 像操作真实DOM一样操作DOM Fragment对象
  content.appendChild(oSpan)
}// 内容处理好了,最后再触发真实DOM的更改container.appendChild(content)
JS中规避重排与重绘
  • 利用变量缓存起来,避免频繁改动
    有时我们想要通过多次计算得到一个元素的布局位置,我们这样做:
    // 缓存offsetLeft与offsetTop的值const el = document.getElementById('el') 
    let offLeft = el.offsetLeft, offTop = el.offsetTop// 在JS层面进行计算for(let i=0;i<10;i++) {
    offLeft += 10offTop  += 10}// 一次性将计算结果应用到DOM上el.style.left = offLeft + "px"el.style.top = offTop  + "px"
  • 避免逐条改变样式,使用类名去合并样式
    const container = document.getElementById('container')
    container.style.width = '100px'container.style.height = '200px'container.style.border = '10px solid red'container.style.color = 'red'// 使用类名const container = document.getElementById('container')
    container.classList.add('basic_style')
  • 将 DOM “离线”
    操作DOM可以先display:none,在操作它的属性,完成后再display:block显示出来。对于频繁操作改变它的属性来说也是不错的优化方法。

  • Flush 队列:浏览器并没有那么简单

    // 这段代码里,浏览器进行了多少次的回流或重绘呢?let container = document.getElementById('container')
    container.style.width = '100px'container.style.height = '200px'container.style.border = '10px solid red'container.style.color = 'red'

    上面代码片段浏览器会进行4次重排或重绘操作吗?我们自己可以动手试一试,其实并不然哦,因为现代浏览器是很聪明的。浏览器自己也清楚,如果每次 DOM 操作都即时地反馈一次回流或重绘,那么性能上来说是扛不住的。于是它自己缓存了一个 flush 队列,把我们触发的回流与重绘任务都塞进去,待到队列里的任务多起来、或者达到了一定的时间间隔,或者“不得已”的时候,再将这些任务一口气出队。所以上面就算我们进行了 4 次 DOM 更改,也只触发了一次 Layout 和一次 Paint。

小结

关于性能优化,真有一种说不清道不明的感觉。这里只是“从URL到页面展示”的角度来学习性能优化的知识,相信内容有很多是浮于表面,具体业务场景肯定是更加复杂多变,所以说前端的性能优化点是错综复杂,比较综合考验个人的工作能力。Anyway,梳理就到此为止,总之,性能优化是一个路漫漫其修远兮的过程,痛并快乐着做吧~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值