多数情况下,浏览器是用单一进程处理用户界面和JavaScript脚本执行。所以,JavaScript的处理时间越久,浏览器等待响应事件就越长。

    简单地说,就是浏览器每次处理<script>标签,就会让页面等待脚本的解析和执行。这个过程页面渲染和用户交互是完全阻塞的。

一、脚本位置

        H4规范指出,<script>标签可以放在<head>和<body>标签内,并允许出现多次。

    1.1 将脚本放在底部

            正常情况下,多个脚本放到<head>内,浏览器会一个一个脚本加载与执行,如果这个过程耗时过长,通常就会显示空白页面。

            IE8,Firefox3.5、Safari4和Chrome2都允许并行下载JavaScript脚本。所以,当加载一个<script>标签时,不会阻塞其他<script>标签,但是会阻塞其他资源的下载,比如图片等。

二、组织脚本

        由于<script>标签初始下载都会阻塞页面渲染,所以减少<script>标签的数量会改善阻塞情况。这不仅仅包含外链脚本,也包含内嵌脚本。

    2.1 单个100k的文件将比4个25k的文件加载快

            考虑到HTTP请求会带来额外的性能开销,单个100k的文件将比4个25k的文件加载快。当依赖多个脚本文件时,尽量合并,用一个<script>标签引入。

    2.2 页面引入的脚本尽量是本网站的,而不是其他网站的。

            同样考虑HTTP请求的开销,请求本网站的脚本文件会比请求其他网站的脚本文件更快。

三、无阻塞的脚本

    

    减小JavaScript文件大小和控制HTTP请求数

            JavaScript倾向于阻塞浏览器的某些处理过程,比如HTTP请求和页面的更新,这是最显著的影响性能的问题。因此,精简代码数量和HTTP请求数很必要。

    加载单个较大脚本文件会锁死浏览器一大段时间

            尽管下载单个较大脚本文件只产生一次HTTP请求,但是会锁死浏览器一大段时间。因此需要逐步加载JavaScript文件,这样做在某种程度上来说不会阻塞浏览器。

        总结的说:就是页面加载完后再加载JavaScript代码。

    3.1 延迟的脚本

        3.1.1 defer属性

            H4给<script>标签定义了一个扩展属性:defer。

            带有defer属性的<script>标签可以放到任意一个位置,因为,当解析到带defer的<script>标签时,只会下载脚本,而不会执行脚本,直到DOM加载完成。因为defer不会阻塞浏览器的其他进程,可以和其他资源并行下载。

            局限性:只有IE 4+ 和Firefox 3.5+的浏览器支持。

    3.2 动态脚本元素

        3.2.1 DOM创建<script>标签

            <script>标签和其他标签并无差异,可以DOM方法创建、移动、删除<script>元素。

            新创建的<script>标签如果下载JavaScript文件,无论何时下载都不会阻塞页面其他进程,放到<head>里都不会影响其他进程。

            通常,新创建的<script>标签放到<head>里比放到<body>里面更保险。因为<body>没完全加载完成时,IE浏览器可能抛出‘操作已中止’的异常。

        3.2.3 readystatechange事件

            IE支持另外一种实现方式,它会触发readystatechange事件。

            <script>元素提供一个readyState属性,它的值在外链文件下载过程的不同阶段会发生变化。

            该属性有五种取值:

                初始状态:uninitialized  开始下载:loading  下载完成:loaded  数据完成下载但不能用:interactive  所以数据已准备就绪:complate



var script = document.createElement("script");
script.type = "text/javascript";

// IE
script.onreadystatechange = function () {
    if (script.readyState == "loaded" || script.readyState == "complate") {
        script.onreadystatechange = null;
        alert("Script loaded.");
    }
}

script.src = "file1.js";
document.getElementsByTagName("head")[0].appendChild(script);



            上述代码这么做是因为在<script>的生命周期内,并非readyState的每个值都会被用到,这些值也不一定会被用到。最有用的两个值是loaded和complate。

            最靠谱的就是检测这两个状态,以确保事件不会处理两次。

    3.3 XMLHttpRequest脚本注入

           先创建XHR对象,再用它下载JavaScript文件,最后通过创建动态<script>元素将代码注入到页面中:



var xhr = new XMLHttpRequest();
xhr.open("get", "file1.js", true);
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4) {
        if (xhr.state >= 200 && xhr.state < 300 || xhr.state == 304){
            var script = document.screateElement("script");
            script.style = "text/javascript";
            script.text = xhr.responseText;
            document.body.appendChild(scripy);
        }
    }
};
xhr.send(null);



        这种方法的好处就是下载下来的脚本文件不会立即执行,另一优点就是它在所有主流浏览器中都可以正常工作。

        缺点是请求的脚本文件与页面要在同一个域内。因此大型的web应用不会用这种方法。

    3.4 推荐的无阻塞模式

        3.4.1 先加载页面初始化所必要的脚本代码,等页面初始化完成,再加载剩下的脚本代码。



<script type="text/javascript" src="loader.js"></script>
<script type="text/javascript">
    loadScript("the-rest.js", function(){
        Application.init();
    });
</script>



            这样确保js执行过程不会阻塞页面其他内容的显示。第二个<script>执行时,DOM结构已经创建完毕,并做好了交互的准备。从而避免了需要另一个事件(比如windows.load)来检测页面是否准备好。

        3.4.2 将loadScript()函数直接嵌入到页面,避免多产生一次HTTP请求。