资讯专栏INFORMATION COLUMN

脚本的加载和执行

TANKING / 858人阅读

摘要:现在对的使用非常普遍,任何一个站点都会请求大量的脚本,而加载和执行的方式也是各不相同,希望读完这篇文章可以对常用的加载和执行方式有一个整体的认识。总结上文主要介绍了动态创建脚本和的方式去创建异步加载和执行脚本的方式。

在打开一个站点的时候,浏览器会去加载各种资源。现在对JS的使用非常普遍,任何一个站点都会请求大量的JS脚本,而加载和执行的方式也是各不相同,希望读完这篇文章可以对常用的加载和执行方式有一个整体的认识。

首先介绍的是html中直接使用

// 使用script的src属性引用外部脚本

这种方式在主流的浏览器可以是并行加载的,但是执行脚本的顺序还是同步的,再加上浏览器是顺序解析页面,所以对于脚本的位置是有一定讲究的:


如果脚本B需要使用到脚本A中的数据(比如fn或者变量),那脚本B必须放在脚本A后面。

因为执行引擎是单线程的,所以在执行JS的时候会阻塞DOM的渲染,导致页面长时间空白。如果不想的话可以考虑把JS放在文档的后面(比如body标签的底部)。

一个外链脚本就涉及到一个请求,再小的请求肯定都会有性能开销,比如请求头,网络时延等等。所以在优化站点性能的时候减少外链也是要考虑的一个点。

【注】:可并行加载的浏览器包括:IE8+、firefox3.5+、safari4+和chrome2+。不同浏览器对于同一个域名下的最大连接数有不同的限制,基本在6个左右。具体可以参见这篇文章。

在程序的世界中,很多场景下同步阻塞就意味着性能问题。在这些场景下其实无阻塞脚本就可以搞定,主进程还是去干当前最主要的事情,即加载和渲染DOM,而无阻塞的脚本可以等到页面加载完再去加载执行,这些场景正是性能优化的点。

这就引申出来了几种无阻塞脚本的方案。

方案一:defer属性 【w3c】【MDN】

这个属性的是承诺用src引的脚本中不会修改DOM。放心的让这个脚本延迟执行吧。具体延迟到文档完成解析后,触发 DOMContentLoaded 事件前执行。

【注1】执行是被延迟了,但是下载还是根据script在页面中的位置。解析到时会去并行下载,但是不会执行。
【注2】由上述定义可以看出来需由src的存在,对于内嵌的脚本是无效的。
【注3】配了defer属性的脚本之间是按照顺序执行的

【测试】 chrome 64.0.3282.119

   
   
   
   
// demo.js
console.log("inner demo.js");

结果:

方案二:async属性 【w3c】【MDN】

配置了async属性是告诉浏览器,这个脚本异步去并行加载,加载完立即异步执行,但是加载的时机是不确定的,所以这个属性比defer更开放。相关测试代码

【注1】因为异步加载完就立即异步执行,所以配了这个属性的脚本之间的关系也是不确定的。所以不能存在依赖async脚本内容的情况。
【注2】执行的时机智能确定在load事件之前,和DOMContentLoaded的时机不能确定
【注3】优先级是高于defer的
【注4】和defer一样,对内嵌脚本无效;不能有document.write改写dom的代码
方案三:动态脚本

动态脚本是我们比较常用的异步加载和执行JS的方式。这种实现要特别注意浏览器的兼容性。简单的实现方式如下:

function loadJs(url,callback) {
    var callback = callback || (() => {});
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = url;

    if(script.readyState){ //IE
        script.onreadystatechange = function () {
            if(script.readyState == "loaded" || script.readyState == "complete"){
                console.log("inner onreadystatechange");
                script.onreadystatechange = null;
                callback();
            }
        };
    } else {
        script.onload = function () {
            console.log("inner onload");
            callback();
        };
    }

    document.getElementsByTagName("head")[0].appendChild(script); // 开始下载并执行
}

loadJs("./server.js");

这种创建的方式,文件在该元素被添加到页面时开始下载,加载完开始执行,并且文件的下载和执行过程不会阻塞其它进程。可以认为这种创建默认加了async属性。我们还可以通过设置async = false的方式取消异步的特性。正因为这个特性,绝大多数场景下都是有益的,但是当我们想使用这种方式去加载多个JS时,并且有先后顺序的时候,可以尝试在callbak里去迭代发请求。

loadJs("f1.js",()=>{
    loadJs("f2.js",()=>{
        loadJs(xxx);
    })
});
方案四:XMLHttpRequest(XHR)注入脚本
搞过Ajax的对XHR应该都很熟悉了,在这就不详细介绍了,需要的去Google一把。

XHR主要是请求脚本,然后我们可以控制请求回来的脚本,在我们需要的时候通过上述动态创建脚本的方式注入到页面中。这种方式最大的好处就是兼容性好。弊端也很明显,必须同源。下面是一个简单实现,没有考虑在创建xhr的兼容性,比如ActiveXObject,有需要的可以去google一把:

var xhr = new XMLHttpRequest(); // 创建xhr对象
xhr.open("get","./server.js",true); // 初始化一个请求, 支持CRUD
xhr.onreadystatechange = function () { 
    if(xhr.readyState == 4){
        if(xhr.status >= 200 && xhr.status < 3000 || xhr.status == 304){
            var script = document.createElement("script");
            script.type = "text/javascript";
            script.text = xhr.responseText;
            document.body.appendChild(script);
        }
    }
}
xhr.send(null); // 发送请求

这地方说明一下xhr.status:

状态 描述
0 UNSENT (未打开) open()方法还未被调用.
1 OPENED (未发送) open()方法已经被调用.
2 HEADERS_RECEIVED (已获取响应头) send()方法已经被调用, 响应头和响应状态已经返回
3 LOADING (正在下载响应体) 响应体下载中; responseText中已经获取了部分数据.
4 DONE (请求完成) 整个请求过程已经完毕.
其它方案: document.write

对于document.write,一般都不推荐使用的,主要是因为存在write方法的脚本可能会在解析的过程中修改DOM,导致一些脚本无法预加载,甚至会导致一些已经预解析和预加载失效。网上也很多关于为什么要避免使用document.write的文章,感兴趣的可以去google一把。

innerHtml

对于innerHtmlouterHTML, 只会以字符串的形式来承载,不会去执行对应的脚本的。

总结:上文主要介绍了动态创建脚本和XHR的方式去创建异步加载和执行脚本的方式。在某些性能调优的情况下还是很有用的,而XHR更是Ajax的核心。

参考

高性能Javascript

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107416.html

相关文章

  • 文档加载

    摘要:完成文档和所有子资源已完成加载。在中可以使用事件来检测文档是否加载完毕在更早的版本中可以通过每隔一段时间执行一次来检测这一状态,因为这条代码在加载完毕之前执行时会抛出错误。 Document.readyState Document.readyState 属性描述了文档的加载状态。当readyState的值变化时,document对象上的readystatechange事件将被触发。 r...

    beita 评论0 收藏0
  • 前端优化-Javascript篇(2.异步加载脚本)

    摘要:下面介绍非阻塞加载脚本技术也就是异步加载。非阻塞加载脚本关于的一篇好文目前所有浏览器都支持属性,但是和中只有在加载外部脚本时才会生效,行内脚本使用是没有作用的。在中,只有外部脚本才会发生阻塞。   上篇博客说过脚本后置可以使页面更快的加载,可是这样的优化还是有限的,如果脚本需要执行一个耗时的操作,就算后置了它还是会阻塞后续脚本加载和执行并且阻塞整个页面。下面介绍非阻塞加载脚本技术也就是...

    wpw 评论0 收藏0
  • 页面生命周期:DOMContentLoaded, load, beforeunload, unloa

    摘要:所以有可能在所有脚本执行完毕后触发。如果用户即将离开页面或者关闭窗口时,事件将会被触发以进行额外的确认。状态表示事件即将被触发。总结页面事件的生命周期事件在树构建完毕后被触发,我们可以在这个阶段使用去访问元素。 页面生命周期:DOMContentLoaded, load, beforeunload, unload 原文地址:http://javascript.info/onload.....

    lx1036 评论0 收藏0
  • 页面生命周期:DOMContentLoaded, load, beforeunload, unloa

    摘要:所以有可能在所有脚本执行完毕后触发。如果用户即将离开页面或者关闭窗口时,事件将会被触发以进行额外的确认。状态表示事件即将被触发。总结页面事件的生命周期事件在树构建完毕后被触发,我们可以在这个阶段使用去访问元素。 页面生命周期:DOMContentLoaded, load, beforeunload, unload 原文地址:http://javascript.info/onload.....

    luckyyulin 评论0 收藏0

发表评论

0条评论

TANKING

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<