高性能网站优化-确保异步加载脚本时保持执行顺序

Posted on:2014-04-10 19:40
7 min read
    前端 web优化

《高性能网站建设进阶指南》

脚本如果按照常规方式加载,不仅会阻塞页面中其他内容的下载,还会阻塞脚本后面所有元素的渲染。异步加载脚本可以避免这种阻塞现象,从而提高页面加载速度。但是性能的提升是要付出代价的。代码的异步执行可能会出现竞争状态。简单地说就是页面内部的脚本需要的标示符如果是在外部文件中定义的,而当外部文件异步加载的时候,如果没有保证外部文件和内部脚本执行顺序,很有可能会出现未定义标示符的错误

当异步加载的外部脚本与行内脚本之间存在代码依赖时,就需要通过一种保证执行顺序的方法来整合这两个脚本。

如何保证执行顺序

当外部脚本按常规方式加载时,他会阻塞行内代码的执行,不会出现因为竞争状态而导致的未定义标示符错误。有几个技术可以帮助我们保证执行顺序。

  • 硬编码回调 (Hardcoded Callback)
  • Window Onlad
  • 定时器 (Timer)
  • Degrading Script Tags

方法1:硬编码回调 (Hardcoded Callback)

让外部的脚本调用内部脚本的函数,以确保代码的顺序执行。例如link

//行内代码
function init() {
  createMenu('examples');
}
var domscript = document.createElement('script');
domscript.src = "menu-with-init.js";
document.getElementsByTagName('head')[0].appendChild(domscript);

//外部文件
function createMenu(id) {
  [...]
}
// callback to the main page
init();
`</pre>

如果开发人员可以同时控制主页面和外部脚本,这种技术是可行的的。但是我们常常会调用第三方的 JavaScript ,比如: jQuery ,我们不可能降回调添加在 jQuery 的文件中。而且这种方法也不太灵活,一旦改变了回调函数需要同时修改外部脚本。  

### 方法2: Window Onload

通过监听 Window 的 onload 事件来触发行内代码的执行。这使得只要确保外部脚本在 window.onload 之前下载执行就能保证执行顺序。有些异步加载技术确保在 window.onload  触发之前加载外部脚步:
  • Script in Iframe 在IE、Firefox、Safari、Chrome 和 Opera 中保持顺序执行

  • Script DOM 在Firefox、Safari 和 Chrome 中保持顺序执行

    使用其中一种技术,再通过 window.onload 触发行内脚本就可以实现并行下载的同时保证执行顺序。查看官网demo。这个例子使用了 Script in Iframe 方法加载外部脚本,几乎在所有的浏览器中它都会阻塞onload事件。外部脚本被嵌入在 menu.php 中,然后用 iframe 加载它而不是直接加载 menu.js 。依据浏览器的差异选用 addEventListener 或者 attachEvent 比简单的地使用 window.onload() 好一些。关于window.onload加载的多种解决方案 »

    Window Onload 整合技术有两个缺点:首先,必须确定异步脚本是通过阻塞 onload 事件的方式加载的。其次,可能会造成行内代码的延迟执行。如果页面还有很多其他的资源,比如图片等,那么外部脚本加载执行结束之后, window.onload内部的代码必须等到页面完全加载之后才能够执行。通常行内脚本最好在外部脚本下载和执行之后立即调用。

    方法3:定时器(Timer)

    定时器技术指的是使用轮询方法来保证在行内代码执行之,前所依赖的外部脚本已经加载。《高性能网站建设进阶优化》一书给出的demo中可以看到link。修改行内代码,添加一个新函数 initTimer ,负责检查依赖的命名空间和标示符是否存在。如果存在,则调用需要调用的函数;如果不存在,就在指定的时间段之后再次调用 initTimer 函数检查命名空间和标示符。

    `function initTimer() {
        if ( "undefined" === typeof(EFWS) ) {
            setTimeout(initTimer, 300);
        }
        else {
            init();
        }
    }
    `

    这个技术也有它的缺点。如果setTimeout方法中设置的事件间隔太小,可能会增加页面的开销。相反,如果设置太大,又可能造成外部脚本加载完成和行内代码开始执行之间的延迟。就上面的例子来说,如果外部脚本加载失败,即行内脚本永远无法检测到指定的命名空间,轮询将会无限进行下去。同时稍微增加了维护的成本,如果外部文件的命名空间和标示符变了,行内代码也要更新。

    方法4:Script Onload

    前面的那些整合技术会增加页面的脆弱性,开销,导致页面的延迟。Script Onload 方法通过监听脚本的 onload 事件解决了所有的这些问题。link。考虑到浏览器之间的差异,添加了 script 元素的 onload 和 onreadystatechange 事件处理程序。onload 在其他浏览器中有效,Opera 两者都有效。

    `var DOMScript=document.createElement("script");
    DOMScript.src="someting.js";
    DOMScript.onloadDone=false;
    DOMScript.onload=function(){
        DOMScript.onloadDone=true;
        init();
    }
    DOMScript.onreadystatechange=function(){
        if(("loaded" === DOMScript.readyState || "complete" === DOMScript.readyState) && ! DOMScript.onloadDone){
            DOMScript.onloadDone=true;
            init();
        }
    }
    
    

Script Onload 是整合异步加载外部脚本和行内脚本的首选。不引用任何外部的标示符,所以维护简单。行内脚本可以在外部脚本加载之后立即执行。同时事件处理也很简单

……未完待续……

Toggle theme