WEB前端

动态加载JS

位置:首页 > WEB前端 > js教程,2015-01-27
为了脚本资源的高并行加载以及提高页面加载速度,我们可能需要动态加载script,其中总是无法避免的一个方法是使用head.appendChild(script)

为了脚本资源的高并行加载以及提高页面加载速度,我们可能需要动态加载script,其中总是无法避免的一个方法是使用head.appendChild(script);因为这种方式可以直接跨域。但是有时候动态加载脚本可能是要保证他们的执行时序。最理想的状态就是所有的脚本都可以在当前http连接数允许的前提下,最大化并行加载量的同时,可以选择按时序执行或按加载完成顺序执行-即先到先执行。


假设我们有3个js,分别为a.js、b.js、c.js,传统的方式是这样的:

<scriptsrc="a.js"></script>

<scriptsrc="b.js"></script>

<scriptsrc="c.js"></script>

这样的加载大概只有ff3.0+和opera的比较新的版本可以保证他们的并行加载,而其他浏览器则只会为他们开启一个http连接,一个一个的加载。

让他们在其他浏览器并行加载的方式有几种,比如借助iframe,比如document.write比如上面说到的head.appendChild(script),比如xhr请求然后eval,比如xhr注入(即xhr和head.appendChild(script)相结合)。其中document.write写入脚本块的方式有些问题,其中比较头疼的是有些浏览器可能会警告说是危险代码,以及一些其他问题。而另外的方式显然都不能很方便的解决跨域问题,所以本文的重点是head.appendChild(script)方式.


这种方式在非ie下我们可以很容易的捕捉script.onload以及script.onerror,但是ie我们只能借助script.onreadystatechange来判断其加载执行状态.而onerror则必须和被请求的.js文件做某些约定才可以更好的判断.该细节不在本文讨论范围...

在话题继续下去前我们应该先明确下.


在动态添加script块的情况下为什么需要script.onload.比如上面的a.jsb.jsc.js这三个脚本假如他们的执行时序是有依赖性的话那么我们就必须保证a.jsb.jsc.js按次序执行...但是一单他们并行加载的话只有firefox和opera才可以保证他们按照请求发起的顺序来执行这些脚本.而其他浏览器则会是无论谁先向服务器发起请求.都只会是一个结果即哪个先加载完毕先执行哪个.为了让我们的LoaderAPI更健壮...不得不在其他浏览器下牺牲并行加载这个好处,而改用加载好一个执行完毕后再加载下一个的方式来处理...当然这里我们有另外的解决方案留到后面说.现在我们把需求简化一下我们仅仅想要script加载并且执行完毕后回调我们指定的方法.

对于非ie非常简单如下面的代码:

varscript=document.createElement('script');

script.onload=function(){alert('callBack');};

script.src="a.js";

document.getElementsByTagName('head')[0].appendChild(script);

就是如此简单对吧.但是遗憾的是ie并不支持script.onload事件这时候我们只好借助

script.onreadystatechange=function(){script.readyState=='某个值'}

这种方式来判断脚本是否加载并执行完毕

此时readyState的值可能为以下几个:

“uninitialized”–原始状态

“loading”–下载数据中..

“loaded”–下载完成

“interactive”–还未执行完毕.

“complete”–脚本执行完毕.


现在问题来了.ie6和ie7ie8有些区别大致分为以下几种情况(以下状态本人测试后又请几位朋友帮忙测试应该可以信任.)

script.src="a.js";

document.getElementsByTagName('head')[0].appendChild(script);

先写src后append的情况下complete和loaded只有一个会出现.他都标志着脚本加载并执行完毕但出现哪个有时候却不能确定和加载脚本时间以及脚本执行时间都有关系.

而document.getElementsByTagName('head')[0].appendChild(script);

script.src="a.js";

先append后给src则比较其开怪ie就可能同时出现complete和loaded而其中ie7和ie8总能保证loaded为最后一个状态即此时ie78我们可以信任loaded状态时脚本已经加载执行完毕.但是ie6就比较郁闷它会因为脚本加载时间和脚本加载后执行时间不同导致loaded和complete的出现先后次序的不同...这时候我们无法得知哪个状态才是脚本执行完毕的状态...为了判断这种状态下脚本是否执行完毕我们需要借助额外的开关变量来信任最后出现的那个状态才是脚本执行完毕的状态.


经过大量测试后得出一个结论即如果你想少惹麻烦的话请先给script设置src属性然后再appedChild他到DOM树中...这样的话我们就只需要这种代码即可以判断脚本执行结束了.

if(/loaded|complete/.test(script.readyState))//ie6ie7ie8通用.好了解决了readyState问题后我们去看看另外的问题

给出的方案如下:

if(script.readyState){//IE

    script.onreadystatechange=function(){

        a.push(script.readyState);

            if(script.readyState=="loaded"||script.readyState=="complete"){

                script.onreadystatechange=null;

                callback&&callback();

            }

        };

    }

else{//Others

    script.onload=function(){

        callback();

    };

}


很明显这里忽略了两个问题.

1.script.onreadystatechange=function(){}这种方式会造成某些版本的ie6无法挽回的crosspageleak内存泄露(暂时我只能确定可以确定sp3补丁的ie6没这个问题)所以即使他具备script.onreadystatechange=null;对于ie6没有意义


2.opera也支持readyState这个事实所以他这段脚本在opera比较新的浏览器下就会出问题...另外一但将来的某个ie版本支持了script.onload我们可能就无法享受到这个好处了


对于1

建议使用attachEvent请记得对于ie6任何非attachEvent方式注册的事件(除硬编码写到html中的)都会引起ie6无法挽回的跨页内存泄露.至于多少就看回调函数所在闭包中的数据量了.作用域链越深受影响的东西就越多...危害也就越大.


对于2

我觉得还是应该优先判断是否支持onload才是正确的思路比如这样:

functionisImplementedOnload(script){

    script=script||document.createElement('script');

    if('onload'inscript) return true;

    script.setAttribute('onload','');

    returntypeofscript.onload=='function';//fftrueiefalse.

}

很显然一来二去的随着我们代码量的增加现在script块动态加载脚本显得越来越靠谱了....


那么我们说说应用中的一些问题


理想状态下所有脚本都应该最大化的利用当前可用的http连接数即有几个我就用机个去并行加载脚本而不是一个一个的在那里阻塞...但是很显然想做到面面俱到是不容易的事情.

那么应该有以下一个流程来处理他们

1.优先考虑xhreval或xhr注入这两种方式可以在保证并行下载资源的同时很方便的控制执行的时序.

2.一但无法解决跨域问题则使用script块动态加载的方式但应知道此种方式非ffopera浏览器一旦要求执行时序则我们无法达成并行加载这一最初的目的.

3.群里的朋友瓶子给出的方案是可以借用postMessageie67使用window.opener漏洞去实现域通信然后借助一个被引入的iframe页面去请求脚本然后把脚本内容传给父页


好吧回到最初的话题请记得对于动态加载scirpt块仍然可能阻塞window.onload的情况当然(硬编码的轻轻是一定会阻塞的啦)我们可以把请求代码写到setTimeout1ms中这样就可以更早的window.onload越早onload的好处就是当我们在window.onunload释放一些事件侦听回调函数以避免内存泄露时变的尤其重要.因为有些浏览器onload不发生就永远不会发生onunload这是个很无奈的问题....这也是为什么推荐少使用iframe去解决一些跨域问题的初衷...因为firame会阻塞主页面的onload....

TAGS:动态加载JS

猜你喜欢

NewHot