性能
性能=习惯+工具
一、无阻塞脚本
众所周知,浏览器解析html页面的步骤,简介一下
- 从head开始,下载外部样式、脚本并逐一执行
- 完成dom树的构造
- 请求外部资源,如图片,音频等
注:指定图片大小,可以避免浏览器自己耗费时间来计算
回归主题,浏览器一般只能同时下载两个资源,在此过程中(包括执行),页面保持阻塞,也即,即使cou空闲,也不能干别的事情。最终结果就是,页面响应缓慢。
我们可以做更多的事,如果没有阻塞,如何才能不阻塞?
- 添加脚本属性
<script async|deffer src="">
。- deffer属性指定,这个脚本不会修改dom,可以延迟执行(页面完成解析时执行)。
- async属性指定脚本将被异步载入,下载完成后立即执行,不会影响页面其余部分的解析。当然如果存在多个这样的文件,它们的执行是无序的。async为html5新增属性。
- 上述属性,都存在兼容性问题。
使用
Ajax
,来异步的载入脚本,脚本的执行时机是可控的,而且浏览器兼容性很好。但是有个致命的弱点,存在跨域限制,一棍子打死。使用
Web Works
,浏览器会创建一个子进程来处理这个异步任务,不阻塞页面渲染,支持跨域访问。如果你的产品,要支持的浏览器比较高级,可以一用。- 有没有一种即兼容、又能跨域,还能无阻塞的方案呢?当然有的。
动态脚本
。- 在Js中创建Script标签,并插入文档,不支持这个的,就不能称为浏览器。
- 对于src属性指定的文件,是没有跨域限制的。不论是script,还是img标签,这也是jsonp的实现跨域的基础。
- 对于动态插入的标签,下载和执行都是异步的,即不阻塞页面解析。
- 下载完成后,代码立即执行,如果是代码无依赖的自执行,是没有问题的。如果作为其它脚本的一部分,则必须要确保其余脚本必须在其之后执行。
状态监听
,可以通过script.onload来监听文件是否下载并执行完成。IE则需要通过readyState来检查脚本状态。
二、计时器与长脚本
单个脚本的执行时间,不应该超过100ms
计时器,是javascript中非常重要的对象,尽管它并不精确。
100ms
,如果一个脚本的执行时间,超过100ms,用户会认为失去对界面的控制。我们需要控制长脚本的运行时间。25ms
,处于对计时器稳定的考虑,计时器的间隔时间,应当>=25ms.同时25ms也是更新UI的必要时间。UI线程
·,浏览器只有一个UI线程,用于JS执行和页面渲染。Ta使用队列来管理UI任务,如果当前有JS在执行,或者UI更新,那么新增的任务被放入队列。但是,当JS处于执行状态,那么,UI更新将不被添加到执行队列。比如点击按钮,虽然点击事件会被添加到队列,但是,按钮的状态更改会被忽略。如果一个脚本需要运行几秒钟,由于单线程的缘故,对用户来说肯定是不可接受的,怎么办?我们可以使用计时器,来
分隔任务
.创建定时器,会重置浏览器限制,和调用栈。在等待的过程中,UI线程可以利用这个时间片,继续处理其它任务。一个页面最好只有一个计时器,以避免多个计时器争夺时间片,造成阻塞。同时低频计时器(>=1s)优于高频计时器(100-200ms).
三、浏览器的重绘与重排
重排一定重绘,重绘不一定重排。
- 浏览器为每个页面建立两颗树,
dom树
、渲染树
重排
,dom元素的几何属性发生改变,引起渲染树相关部分的失效和重排。重绘
,将元素更改的属性,绘制到屏幕上。- 为了最小化重排,我们需要使用一些方法来使元素
脱离文档
。在这里对元素应用多重修改,而不会导致多次重排。(仅在带出元素和带入元素两步,引起重排)- 将元素设置为display:none,这样的元素不会出现在渲染树中。
- 使用
document fragment
,文档片段,在dom树外,修改元素。 - 使用cloneNode来创建一个不在dom树总的节点。
- 使用绝对定位,使元素脱离文档流。避免引起大面积重排。
- 应当
批量
的修改属性,而非频繁的逐一修改。 - 浏览器,默认会使用队列来缓存修改信息,但是如果当你使用如offsetTop,clientTop,scrollTop等属性时,会
强制刷新
队列,已获得最新的布局信息。
参考书籍,《高性能Javascript》 Nicbloas