前端优化只要在两方面进行,一是加载速度优化,二是渲染速度优化。在进行优化前,先掌握好2个基本理论知识
理论知识(一):浏览器的完整加载渲染过程
①输入url,发送请求
②加载(即下载)整个.html文件
③加载完后解析(即运行)html,并在解析的过程中构建DOM树
·JavaScript是单线程的。浏览器是多线程的:有的线程负责加载资源,有的线程负责执行脚本,有的线程负责渲染界面。
·浏览器按从上之下(深度遍历)的原则解析各个html标签
·解析标签的过程就是构建DOM树的过程
·解析遇到link、script、img标签时,浏览器会向服务器发送请求资源。
script加载时不影响其他资源加载,但由于不知道js中的执行内容,所以需要等JS加载并执行完后才会继续解析和渲染。
script的执行会阻塞html解析、其他下载线程以及渲染线程。
link加载完css后会解析为CSSOM(层叠样式表对象模型,一棵仅含有样式信息的树)。css的加载和解析不会阻塞html的解析,但会阻塞渲染。
img的加载不会阻塞html的解析,但img加载后并不渲染,它需要等待Render Tree生成完后才和Render Tree一起渲染出来。未下载完的图片需等下载完后才渲染。
④当css解析为CSSOM后,html解析为DOM后,两者将会结合在一起生成Render Tree(渲染树)。
⑤Layout(reflow): 计算出Render Tree每个节点的形状和位置。(很耗性能)
⑥Painting(repaint):浏览器绘制这些元素的样式,颜色,背景,大小及边框等。(很耗性能)
⑦Composite(层叠时):浏览器会将各渲染层的信息发送给GPU,GPU会按照合理的顺序合并图层然后显示到屏幕上。(GPU合成图像,单独线程,更流畅,但耗内存)
理论知识(二):浏览器的渲染性能指标
大多数电脑显示器的刷新频率是60Hz,大概相当于每秒钟重绘60次,因为小于这个频率,页面的渲染就会出现卡顿现象,影响用户体验。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是1000ms/60,约等于16.6ms。该指标是游戏开发中的最重要的指标。
*前端加载优化:
·使用外联CSS和JS:让浏览器缓存,减少http请求。可利用webpack在引用的资源后面自动增加hash值,实现引用不变的基础上对浏览器缓存的资源进行更新。对于缓存容量小的问题,可以考虑缓存在localStorage。
·合并CSS、JS:减少http请求。
·按需加载:webpack的打包思路就是从程序逻辑入手:入口文件 => 分析代码 => 找出依赖 => 打包,这样代码里不引用的模块就不可能被打进包里,有效减少体积。
·按JS模块加载:例如echart中如果只用k线图就只加载k线模块。
·压缩HTML、CSS、JS:减少资源大小。
·图标采用base64:减少http请求。
·图标采用雪碧图(svg、font-icon):减少http请求,减少资源大小。
·图片压缩:picdiet(https://www.picdiet.com/zh-cn),是用JS编码,无大小、尺寸、数量限制,默认可平均减少50%体积,清晰度基本没损失,没有兼容性问题,也可以调节压缩比。
·避免图片和iFrame等的空src:减少http请求。
·图片:滚屏懒加载。
·CDN加速:常用资源使用CDN加速提高资源响应速度,如jquery、echarts。(http://www.bootcdn.cn/)
·BigRender首屏渲染优化:html, js, css和图片都放在textarea中懒加载(https://segmentfault.com/a/1190000006744741)。
·增加Loading进度条:将加载情况呈现给用户。
*HTML、CSS结构优化:
·页面的标签越少,页面的加载速度就越快,响应也更加迅速。
·css不能阻塞加载,所以将css放在头部,防止白屏和重排重绘造成的内容闪烁现象。
·尽量保持class的简短,如:.box:nth-last-child(-n+1) .title,改为:.final-box-title。
·用flex布局取代浮动布局。
·移除空的CSS规则:空的CSS规则增加了CSS文件的大小,且影响CSS树的执行。
·不声明过多的font-size:过多的font-size引发CSS树的效率。
·值为0时不需要任何单位:为了浏览器的兼容性和性能,值为0时不要带单位。
·display属性会影响页面的渲染,需合理使用:
①display:inline后不应该再使用width、height、margin、padding以及float
②display:inline-block后不应该再使用float
③display:block后不应该再使用vertical-align
④display:table-*后不应该再使用margin或者float
*DOM性能优化:主要是防止重排和重绘
·图片、音频和视频的宽高在加载完成之前为0,所以静态资源加载前需规定图片的大小。
·尽量避免重设图片大小:多次重设图片大小会引发图片的多次重绘
·多使用requestAnimationFrame(待学习)
·如果同时添加父元素和子元素,要在内存中先将所以子元素添加到父元素下,将父元素一次性加入DOM树。
·如果同时添加多个平级子元素,要先将平级子元素加入文档片段,再将文档片段整体加到页面。具体为:
①创建文档片段:var frag=document.createDocumentFragment();文档片段: 内存中临时存储多个平级子元素的虚拟父元素。
②将平级子元素,先追加到frag下: 用法同普通父元素
③将文档片段,整体添加到页面
·先把dom节点display:none;(会触发一次重排)。然后做大量的修改后,再把它显示出来。
·clone一个dom节点在内存里,修改之后;与在线的节点相替换。
·尽量使用ID选择器,ID选择器是最快的。
·每次Dom选择都要计算,缓存他,避免强制同步布局(force reflow)。
·对于修改元素多个样式,可以使用cssText属性,避免强制同步布局。例如如下,触发3次重排:
var el = document.getElementById('myDiv');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
改成这样,只需1次重排:
var el = document.getElementById('myDiv');
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';
将多个样式变化定义到一个class中,再通过className添加class也行。
·动画多使用transform和opacity,他们只会引起合成,不会引起布局和重绘。
·如果图层中某个元素需要重绘,那么整个图层都需要重绘。用translateZ(0)手动创建渲染层,减少需渲染的像素数,还可以用GPU加速。但是不要滥用这个属性,否则会大大增加内存消耗。(详情:https://developers.google.cn/web/fundamentals/performance/rendering/simplify-paint-complexity-and-reduce-paint-areas)
·不要使用table布局,一个小改动会造成整个table的重新布局。
·用css动画而不是js动画:css动画有一个重要的特性,它是完全工作在GPU上。因为你声明了一个动画如何开始和如何结束,浏览器会在动画开始前准备好所有需要的指令;并把它们发送给GPU。而如果使用js动画,浏览器必须计算每一帧的状态;为了保证平滑的动画,我们必须在浏览器主线程计算新状态;把它们发送给GPU至少60次每秒。除了计算和发送数据比css动画要慢,主线程的负载也会影响动画; 当主线程的计算任务过多时,会造成动画的延迟、卡顿。所以尽可能地使用基于css的动画,不仅仅更快;也不会被大量的js计算所阻塞。
·减小复合层的尺寸:可以将图片的尺寸减少5%——10%,然后使用scale将它们放大;用户不会看到什么区别,但是你可以减少大量的存储空间。
·采用虚拟DOM技术:例如Vue、React等框架就采用了虚拟DOM。
*script优化:
·我们知道脚本加载和运行会阻塞页面,所以应该把<script>标签放到最后。
·JS:throttle函数(节流):每XX秒内只执行一次;(https://segmentfault.com/a/1190000006722279)
·JS:debounce函数(防抖):当连续触发函数调用时,在最后一次触发的XX秒以后才开始一次调用。(https://segmentfault.com/a/1190000006732819)
·对于多条件判断,字典比if-else快。
·避免快速连续的布局:
改善前:function resizeWidth() {
// 会让浏览器陷入'读写读写'循环
for (var i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = box.offsetWidth + 'px';
}
}
改善后:var width = box.offsetWidth;
function resizeWidth() {
for (var i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = width + 'px';
}
}
·深入原形链越深,搜索的速度就会越慢。记住,搜索实例成员的过程比访问直接量或者局部变量负担更重,所以增加遍历原形链的开销正好放大了这种效果。
·函数作用域链的搜索也会消耗性能,全局变量总是在作用域链的最后,所以耗时最久。最好尽可能使用局部变量。一个好的经验法则是:用局部变量存储本地范围之外的变量值。
·成员嵌套越深,访问速度越慢。 location.href 总是快于window.location.href ,而后者也要比window.location.href.toString()更快。如果这些属性不是对象的实例属性,那么成员解析还要在每个点上搜索原形链,这将需要更长时间。
·计算简化,如:for(var i=2;i<=Math.sqrt(n);i++){if(n%i==0){return false}}
·前面提到每帧的渲染应该在16ms内完成,但在动画过程中,由于已经被占用了不少时间,所以JavaScript代码运行耗时应该控制在3-4毫秒。如果真的有特别耗时且不操作DOM元素的纯计算工作,可以考虑放到Web Workers中执行。
*服务端设置优化:
·缓存一切可缓存的资源:http缓存。
·使用长Cache,避免304重定向:在移动端网络不稳定的前提下,多一次请求,就多了一部分加载时间。
·使用DNS缓存:减少浏览器会在DNS解析中消耗的时间。
·启用GZip:GZip是http协议的一部分,用来压缩网页大小的。
·图片压缩:默认生成缩略图传前端。
·减少Cookie:Cookie会影响加载速度,所以静态资源域名不使用Cookie。
·域名解析:减少域名解析次数——减少跨站外部资源的引用。
·创建连接:减少连接创建次数——使用Keep-Alive避免重复连接。
·等待响应:提高服务器运行速度——提高数据运算及查询速度。