前端性能优化笔记(2)

HTML 页面加载渲染过程

浏览器从请求到生成页面发生了以上的过程。首先,我们在地址栏里输入www.baidu.com ,会向百度服务器解析的ip地址发送请求,接到我们的请求后,会向我们返回一段HTML的文档。浏览器接收到启用HTML Parse解析器进行解析,通过词法分析的过程,将内容分析成不同的token,然后根据HTML的文档,从上到下依次nextToken进行解析token,并获取下一个token的位置,所以我们的DOM tree是通过词法分析token一步一步添加的。而类似于linkscript src引用web资源的地址会进一步的由浏览器发送请求cssjs相关资源,将请求回来的js资源利用V8内核引擎执行js代码。请求回来的CSS资源则会生成相应的CSSOM,前面的DOM tree生成完毕后浏览器并不会直接渲染出来,而是会等待CSSOMcss tree)生成后进行合并,再渲染出来。

HTML渲染的一些特点

顺序执行,并发加载

前面讲到浏览器会将HTML文档进行nextToken一步一步解析每个标签的token,所以是顺序执行。并发加载指的是同时加载多个资源,这里要注意的是,我们平时将web资源托管在CDN上,由于受到浏览器的域名影响,每个域名都有并发请求的数量上限。导致很多资源没有做到同时的并发请求,所以一般会设置3~4CDN域名。

是否阻塞以及依赖关系

我们要考虑几个问题:

css加载是否会阻塞js?

答案是会的。它不会阻塞js的加载,chrome浏览器中有个webkit upload preScaning(预先扫描器),它能够在解析当前文件或者文档的时候,预先扫描下一部分的代码,如果存在外部资源,会并发进行加载,但是会阻塞js的执行!打个比方,如果js文件内有修改dom样式的代码,会依赖于之前已绘制的DOM样式的基础上去修改的。js本身是无法判断DOM tree样式是否存在及修改 ,

css加载是否会阻塞页面的渲染?

css放在head中阻塞页面的渲染,它会等待CSS样式表加载完成形成CSSOMcss tree)树再去加载DOM,但是这种方式也是推荐的,会让当前加载的HTML页面DOM带样式的进行加载。
在一些情况下,我们的页面已经加载完成了,但是样式没有完整,突然闪的一下,页面重新渲染。这种情况的原因可能是网速较慢css资源没有下载下来,导致CSSOMcss tree)没有生成,最后解析完毕合并DOM treeCSSOM造成页面闪动的现象。根本原因是没有遵循好依赖关系,如果我们把css代码放在<head>中去引入的话,浏览器是会优先解析css样式表生成CSSOMcss tree),再解析DOM tree最后合并成render tree渲染页面。

js执行是否存在依赖关系?

由于js会阻塞后续代码的执行,故script标签属性有一个async异步加载属性,使用此属性的web资源会放弃依赖关系,保持着’哪个资源先加载完就执行哪个’的思想。所以在使用该属性的时候,我们要梳理好各个js文件的依赖关系。
开发SPA单页应用的时候,首屏是不会加载全部外部资源,在A页面的时候,只异步加载A页面所需的资源,在B页面的时候,只异步加载B页面的资源,这也是性能优化极为重要的一点。

js阻塞

直接引入的script src会阻塞页面的渲染。实际上我们的js代码很可能去调用document.ready去修改DOM样式的,依赖于当前页面DOM tree。可能插入节点或者是操作节点,影响后续DOM样式排版,所以按常理而言,这样阻塞是合理的。浏览器会等待js操作DOM节点完毕再去进一步的渲染HTML节点。和CSS阻塞JS执行的原因大致上是一样的。根本原因是浏览器HTML parse认为js文件会去操作HTML DOM结构,故先执行js代码导致阻塞HTML渲染。
异步加载可以避免这种情况:Deferredasync,这两天阮大的blog被Ddos攻击了,只好给这两个链接,可以去看看。

懒加载与预加载

懒加载

在日常工作中,懒加载和预加载相信很多人用过了,其目的都是为了避免加载非必要资源,节省页面性能。 而且页面加载并发是有上线数的,如果被img占用了jscss相关资源,从而导致jscss无法及时加载、所以,懒加载就被开发出来了。实现方法普通是根据当前可视窗口的位置,依需替换imgsrc占位符。将data-img自定义属性替换到src上。再动态的进行请求。在购物类网站、尤其图片社交类网站的较多。
可以参考以下原生方法,但是要考虑图片占位的因素。否则会引起抖动和不好的用户体验。

<img src="" class="lazyload-item" lazyload="true" data-original="xxx.png">

var viewHeight = document.documentElement.clientHeight;//获取当前窗口的屏幕高度

function lazyload() {
var eles = document.querySelectorAll('img[data-original][lazyload]');
Array.prototype.forEach.call(eles, function (item, index) {
var rect
if (item.dataset.original === '')
return
rect = item.getBoundingClientRect();
if (rect.bottom >= 0 && rect.top < viewHeight) {
!function () {
var img = new Image();
img.src = item.dataset.original;
img.onload = function () {
item.src = img.src;
}
item.removeAttribute('data-original');
item.removeAttribute('lazyload');
}()
}
})
}

lazyload();
document.addEventListener('scroll', lazyload);

预加载

与懒加载相反,预加载是提前进行加载,提升用户体验。在大型展示类页面,音频、视频、游戏、webGL模型等场景,优先预加载可以提升非常不错的体验度,这也是预加载适合的场景。图片预加载的方式其实很简单,新建一个img标签,填上srcdisplay:none,第二次使用的时候,浏览器就会优先使用该缓存,这种方式的弊端是不能控制其周期。第二种方式是新建浏览器请求,原理是新建一个xmlHttpRequest()请求。保存其请求状态信息,进行相应回调。弊端是存在跨域问题,preload.js解决了该问题,只要在方法队列里加一个false参数即可不走xmlHttpRequest路线。还可以设置延迟预加载,避免在页面第一次加载的时候网速占用大影响其他资源的加载速度,具体方法可自行官网学习。

图层渲染(回流与重绘)

浏览器解析DOM tree完毕之后对DOM进行分割处理为多个图层,然后对每个图层的样式进行计算,拿到节点计算样式结果后为每个节点生成图形和位置,(Layout–布局和回流),再将每个节点绘制填充到图层位图中(Paint SetupPaint–重绘),接下来图层纹理上传至GPU,符合多个图层到页面上生成最终屏幕图像(Composite Layers–图层重组),整个就是浏览器渲染的过程。站在性能优化的点上看,一个页面的如果有多次重绘与回流是非常消耗性能的,但是重绘的代价比回流低得多,所以要尽量去创建图层避免多次回流,减少回流的次数。

回流的触发机制

回流是怎么触发的呢? 当render tree的部分或者全部,因为元素的规模尺寸,布局,隐藏等属性改变需要重新构建,这就被称为回流(reflow),还有当页面布局和几何属性改变时就需要回流。
一句话概括就是:当页面布局变化的时候,必定会触发回流!

哪些CSS属性会触发重布局?
概括下:
1.盒子模型相关属性会触发重布局.
CSS属性:width,height,padding,margin,display,border-width,border,min-height;
2.定位属性及浮动也会触发重布局.
CSS属性:top,bottom,left,right,position,float,clear,
3.改变节点内部文字结构也会触发重布局.
CSS属性:text-align,overflow-y,font-weight,overflow,font-family,line-height,vertival-align,white-space,font-size;
总结出一个规律了吗?只要是会’动’的CSS属性,都会触发回流!

重绘的触发机制

回流是’动’,那重绘就是外观了,当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘.
color,border-style,border-radius,visibility,text-decoration,background,background-image,background-position,background-repeat,background-size,outline-color,outline,outline-style,outline-width,box-shadow…这些属性都可以触发重绘。

创建图层

那怎么去创建图层呢?以标准化的浏览器–>Chrome为例,含有以下条件(不限于)
1.混合插件(如Flash
2.3D或透视变换(perspective transform)CSS属性
3.使用加速视频解码的<video>节点
4.拥有3D(WebGL)上下文或加速的2D上下文的<canvas>节点
5.拥有加速CSS过滤器的元素(filter: grayscale(100%) opacity(50%)…)
6.对自己的opacity做CSS动画或使用一个动画webkit变换的元素.
7.元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
8.元素有一个包含复合层的后代节点(一个元素拥有一个子元素,该子元素在自己的层里)
Chrome开发者工具里有一个Performance工具,用于监测图层的创建和渲染的整个过程,有兴趣的朋友可以去玩玩。

总结优化点

  1. translate替代top改变
  2. opacity替代visibility
  3. 不要一条一条地修改 DOM 的样式,预先定义好 class,然后修改 DOMclassName
  4. DOM 离线后修改,比如:先把 DOMdisplay:none (有一次 Reflow),然后你修改100次,然后再把它显示出来
  5. 不要把 DOM 结点的属性值放在一个循环里当成循环里的变量
  6. 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
  7. 动画实现的速度的选择
  8. 对于动画新建图层
  9. 启用 GPU 硬件加速