渐进式的脚本加载

    通过之前几篇性能指标分析系列的文章可以发现,无论是对于哪个性能指标来说,脚本的影响都是最大的。实际的线上监控数据(图1)也确实能够论证这一点,例如某个页面DOM Ready的时间在4.1s左右,而外链脚本的加载和执行则消耗了2.8s左右,比例高达68.74%。也就是说假如该页面没有脚本的话,页面的DOM Ready时间将会大大缩短。实际的页面中,脚本在加强和提高交互体验方面又是不可或缺的,因此需要从脚本的合理使用方式着手来改善脚本的使用对整体页面性能的影响。

 

image

    按照Javascript的加载方式大概可以分为即时加载和动态加载两类,即时加载指的是在页面中直接使用<script>标签引入资源,随着页面的解析而引入的;动态加载则是指使用除即时加载之外的方式,例如创建<script> DOM结点载入脚本。此外,引入脚本的时机也是不同的,不同时机引入脚本对性能的影响也是迥异的。根据引入脚本的方式以及时机,本文将脚本的引入分为以下几个阶段:即时加载、解析进行时动态加载、解析完成时动态加载、页面完成时动态加载、按需加载。通过调整页面中脚本在不同阶段的分布可以达到优化性能的目的。

即时加载(Page Parsing Load)

    即时加载指的是在页面加载解析的过程中使用<script>标签直接加载的脚本(包括document.write方式),例如在页面头部、或其他位置直接加载的脚本,而不是在DOM Ready之时或者更晚再加载。

    即时加载脚本对页面性能的影响如下:

    (1) 即时加载脚本会阻塞其他资源加载,而且在IE6下,多个脚本之间只能串行加载。即使是使用document.write方式也只是解决了脚本间的并行下载,依然会阻塞其他资源的加载;

    (2) 在加载、解析和执行的过程中,整个页面的渲染、解析和交互状态会被“冻结”,即使是使用document.write方式;

    (3) 通过《从用户体验出发的性能指标分析-DOM Ready》一文的分析可以得出,即时加载的脚本对DOM Ready指标的影响是非常大的,它会显著的推迟DOM Ready,从而影响其他在DOM Ready中初始化的交互功能;

    鉴于即使加载脚本对性能影响之巨大,在使用这种方式应该牢记以下原则:

    即时加载的脚本数应该尽可能的少,需要使用这种方式加载的脚本应该是页面中的核心脚本,为其他脚本所依赖,例如核心脚本库。

解析进行时动态加载(Page Parsing Dynamic Load)

    解析进行时动态加载指的是在解析的过程中即可触发加载,但是又不是直接使用<script>标签进行加载的方式,常用的方式有DOM Element-即动态创建<script>标签,还有iFramed JS-即在iframe中引入脚本,此外还有其他的方式,Steve在《Loading Scripts Without Blocking》一文中有很好的总结。

    与即时加载相比,该方式的优势在于:

    (1) 动态加载的方式属于无阻塞加载,脚本在加载的过程中不会阻塞其他资源的加载(包括其他脚本),可以充分的并行加载,有利于提高整体性能;

    (2) 动态加载的脚本在加载的过程中不会阻塞页面的渲染和交互;

    (3) 动态加载的脚本在加载的过程中不会阻塞浏览器的解析,因此脚本对DOM Ready指标的影响也减少了,使DOM Ready能够更早的触发;

    不过,该方式也存在一些问题,例如脚本如果在DOM Ready前加载完成,则在执行的时候依然会“冻结”整个页面的解析、渲染和交互。

    权衡利弊,使用该方式加载的脚本应该符合以下原则:

    应该属于核心功能,而且相关的脚本需要在页面解析的过程中即开始初始化和事件注册,否则用户将无法交互。

    在实际的优化过程中,应该尽可能的将即时加载的脚本挪到该方式下加载,不过出于对以上提到的问题的考虑,非核心功能的脚本则建议挪到下一个阶段。

解析完成时动态加载(DOM Ready Load)

    解析完成时加载本指的是在页面解析完成后即DOM Ready事件触发的时候加载的脚本。DOM Ready事件触发时,通常整体页面基本渲染完成了,因此在此时加载脚本有以下优势:

    (1) 由于是在页面解析渲染完成后加载的脚本,因此脚本的执行不会影响页面的渲染;

    (2) 在加载时机上,与前一阶段相比还不至于太晚而影响用户体验;与后一阶段-页面加载完成时加载相比,则又要提前一些;

    使用该方式加载的脚本应该符合以下条件:

    脚本属于非核心功能模块,而且脚本对于功能本身而言是增强型的而不是完全的依赖,即脚本的延迟加载不会导致功能完全不可用。例如对于非核心功能的用户输入模块,验证相关的脚本就符合这类条件,可以采用这种加载方式。

页面完成时加载(Page Completed Load)

    页面完成时加载指的是在页面完全加载完成后(包括图片、iFrame等资源)加载的脚本,或者可以说是在window的onload事件触发时进行加载。在该阶段加载脚本的优势如下:

    (1) 整个页面的所有资源加载完成,因此在此阶段加载的脚本对页面没有任何性能影响;

    (2) 如《从用户体验出发的性能指标分析-Page Load》中提到的,在Page Load后加载资源,在某些浏览器中可以让资源的加载过程对用户透明,优化用户的感官体验;

    但是由于window的onload事件的触发是由最晚加载的资源的时间所决定的,因此进入该阶段的时间是不稳定和不可控的,因此在本阶段载入的脚本应该如下要求:

   (1) 与首次加载功能无关的脚本(如某些用户操作后才会使用的脚本)或者与用户交互无关的脚本(如统计脚本);

    (2) 下一个阶段-按需加载中的脚本需要进行预加载的脚本,预加载可以改善网速较慢用户的体验,不至于需要的时候却要等待过长的时间;

按需加载(On-Demand Load)

    按需加载,顾名思义指的是当用户触发了某个功能时才加载对应的功能,使用这种方式时需要合理选择脚本加载触发点。

    按需加载的最大优势在于减少了不必要的资源请求。但是按需加载本身如果使用不当也会影响用户体验,因为加载的时机在用户执行某个操作之后,如果用户的网速比较慢的话,加载脚本可能需要等候较长的时间,而用户则不得不为此付出代价。因此,如果要使用按需加载则需要选择正确的脚本加载时机。例如对于一些提交数据的操作,可以在用户开始填写数据的时候即开始加载脚本,而不需要等到用户填写完成真正提交时才开始加载脚本。

    此外,解决体验问题的另一个方式是将脚本的加载挪到上一个阶段中进行。

总结

    脚本的加载可以分为即时加载、解析进行时动态加载、解析完成时动态加载、页面完成时动态加载、按需加载几个阶段,优化的目标是让每一个脚本找到它应该属于的加载阶段,从而达到性能和体验的平衡。优化的原则就是尽一切努力减少即时加载,将即时加载统统分散到其后的几个阶段中加载。

    与时间管理的“四象限”法则类似,以上的几个阶段实际上也可以概括为四个象限:

    第一象限 – 即时加载

    第二象限 – 解析进行时动态加载

    第三象限 – 解析完成时动态加载

    第四象限 – 页面完成时加载以及按需加载

    优化的目标即时减少第一象限中的脚本数,尽可能的将脚本放在第二象限,而一些不太重要的脚本则放到第三、四象限中。

You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

Leave a Reply

您必须 登录 才能发表评论。