Javascript的加载

最新博客站点:欢迎来访

1. 浏览器加载

(1) 同步加载

在网页中,浏览器加载js文件的方式是通过<script>标签。如下所示:

//内嵌脚本
<script type="text/javacript">
    // code here!
</script>
//加载外部脚本
<script type="text/javascript src="path/demo.js"></script>

<script>标签很方便,只要加入后,浏览器便可读取并运行,但是在读取的时候,浏览器是按照<script>标签的出现顺序,读取Javascript文件,然后立即运行,导致在多个文件相互依赖的情况下,依赖性最小的文件必须放在最前面,依赖性最大的必须放在最后面,否则代码会报错,这一点,想必大家在使用bootstrap的时候都深有体会。另一方面,浏览器采用同步模式加载<script>标签,也就是说,页面会等待JavaScript文件加载完成,然后再运行后面的代码。当存在很多个<script>标签时,浏览器无法同时读取,必须读完一个再读取另一个,造成读取时间大大延长,页面响应缓慢,影响用户体验。同步模式又称阻塞模式,会阻止浏览器的后续处理,停止后续的解析,只有当前加载完成,才能进行下一步操作,所以默认同步执行才是安全的。但这样如果js中有输出document内容、修改DOM、重定向等行为,就会造成阻塞。所以一般建议把<script>标签放在<body>结尾处,这样能减少页面阻塞。

(2)异步加载

为了解决这一问题,ES5中采用了DOM方法,动态加载JavaScript脚本文件

function loadScript(url) {
    var script = document.createElement("script");
    script.type="text/javascript";
    script.src=url;
    document.body.appendChild(script);
}

这种方式通过创建一个新的<script>标签,并设置其src属性,异步读取javacript文件

这样不会造成页面阻塞,但会有另一个问题,如果其他脚本文件依赖于它,此时无法保证此脚本什么时候能够载入完毕。

另一种加载方式是利用defer和async属性,使脚本异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。defer和async的区别是: defer要等到整个页面在内存中正常渲染结束(DOM结构完全生成,以及其他脚本执行完成),才会执行;async一旦下载完成,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。即defer是渲染完再执行,async是下载完就执行。另外,如果有多个defer脚本,会按照它们在页面中出现的顺序加载,而多个async脚本是不能保证加载顺序的。

IE9及以下版本在延迟实现方面存在一些相当糟糕的错误,导致执行顺序无法保证。 如果你需要支持<= IE9,我建议不要使用defer,如果执行顺序很重要,请包含没有属性的脚本。

<script src="path/demo.js" defer></script>
<script src="path/demo.js" async></script>

如何选用defer和async。如果使用的script是个模块,并且不依赖任何其它script文件时使用async;如果该脚本依赖其它script或则被其它script依赖,就使用defer;倘若脚本文件很小且被一个async script依赖,就使用内嵌script把该文件放在所有async script前面。

另外一种方法是onload事件的异步加载。

(function(){
    if(window.attachEvent) {
        window.attachEvent("load", asyncLoad);
    } else if(window.addEventListener) {
        window.addEventListener("load", asyncLoad);
    } else {
window.onload = asyncLoad;
} var asyncLoad = function() { var script = document.createElement("script"); script.type="text/javascript"; script.async = true; script.src = ('https:'==document.location.protocol ? 'https://ssl' : 'http:www') + '.baidu.com/demo.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(script, s); }; )();

这种方法是把插入script的方法放在一个函数里面,然后放在window的onload方法里面执行,这样就解决了阻塞onload事件的触发问题。

由于Javascript的动态性,还有很多异步加载方法:XHR Injection、XHR eval、Script In Iframe、document.write("<script type='text/javascript' src=' '")等;

XHR注入:通过XMLHttpRequest来获取Javascript,然后创建一个script元素插入到DOM结构中。ajax请求成功后设置script.text为请求成功后返回的responseText.

var createXHR  = function() {
    var obj;
    if(window.XMLHttpRequest)
        obj = new XMLHttpRequest();
    else
        obj = new ActiveObject("Microsoft.XMLHTTP");
    return obj;
};
var xhr = createXML();
xhr.open("GET", "http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js", true);
xhr.send();
xhr.onreadystatechange = function() {
    if(xhr.readyState == 4 && xhr.status == 200) {
        var script = document.createElement("script");
        script.text = xhr.requestText;
        document.getElementsByTagName("head")[0].appendChild(script);
    }
}

XHR eval(): 与XHR Injection对responseText的执行方式不同,直接把responseText放在eval()函数里面执行。

var createXHR  = function() {
    var obj;
    if(window.XMLHttpRequest)
        obj = new XMLHttpRequest();
    else
        obj = new ActiveObject("Microsoft.XMLHTTP");
    return obj;
};
var xhr = createXML();
xhr.open("GET", "http://cdn.bootcss.com/jquery/3.0.0-beta1/jquery.min.js", true);
xhr.send();
xhr.onreadystatechange = function() {
    if(xhr.readyState == 4 && xhr.status == 200) {
        eval(xhr.responseText);
        $('#btn').click(function() {
            alert($(this).text());
        });
    }
}

Script In Iframe: 在父窗口插入一个iframe元素,然后再iframe中执行加载JS的操作。

var insertJS = function(){
    alert($);
};
var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
var doc = iframe.contentWindow.document;//获取iframe中的window
doc.open();
doc.write("<script>var insertJS = function(){};<\/script><body onload='insertJS()'></body>");
doc.close();

2. 延迟加载

有些JS代码在某些情况下需要使用,并不是页面初始化的时候就要用到。延迟加载就是为解决这个问题。将JS切分成许多模块,页面初始化时只将事件处理程序添加到UI元素上,然后其它JavaScript代码的加载延迟到第一次用户交互或者其他条件需要用到的时候再加载。类似图片的懒加载。这样做的目的是可以渐进式地载入页面,尽可能快地为用户提供目前需要的信息,其余部分的内容可以在用户浏览该页面时在后台载入。

JavaScript的加载分为两个部分:下载和执行脚本,异步加载只解决了下载的问题,但是代码在下载完成后就可能会立即执行,在执行过程中浏览器储与阻塞状态,响应不了任何需求。为了解决JavaScript延迟加载的问题,可以利用异步加载缓存起来,所以不会立即执行,然后在第一次需要的时候再执行。

第二部分内容的载入可以用创建动态脚本的形式:

window.onload = function() {
    var script = document.createElement("script");
    script.type="text/javascript";
    script.src="demo.js";
    document.documentElement.firstChild.appendChild("script");
}

3. 按需加载

可以通过创建一个require方法,包含需要加载的脚本名称和附加脚本加载完成后需要执行的回调函数。

function require(file, callback) {
    var script = document.getElementsByTagName("script")[0];
    var newjs = document.createElement("script");

    newjs.onload= function() {
        callback();
    };
    newjs.src=file;
    script.parentNode.insertBefore(newjs, script);
}

参考:

https://www.jb51.net/article/107680.htm

http://es6.ruanyifeng.com/#docs/module-loader