前端不要性无能

性能

性能=习惯+工具

一、无阻塞脚本

众所周知,浏览器解析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

posted @ 2014-09-14 11:51  宏图志远  阅读(2229)  评论(10编辑  收藏  举报