现代浏览器JS异步加载方案

好,不说废话,要凌晨了我快点写完,关于JS加载阻塞之类的不做科普,本文也不讨论IE9以下的浏览器。

 

headjs:“异步、并行加载并按你的顺序执行”,这个是最符合广大码农需求的,只是它越来越臃肿,连CSS Respone什么的都加进去了,没必要那么复杂

Loadjs:今天刚刚出现在OSC首页本人疯狂测试后发现,如果需要并行加载就是乱序的(哪个JS先下载完就先执行谁),如果要顺序下载就不能并发,经测试是下载一个执行一个,每个JS文件之间有10ms的空隙(JS下载和加载时所有渲染操作都要暂停,连下载都不行)

还有一个不说了,那是给No-Backend的网站专用的,很庞大一库,功能很强大也很复杂

 

综上所述,如果你在乎性能和代码质量,一个都不要用。

然而这些库都解决了一个致命问题——内联JS的异步加载,这就很纠结了对吧。

 

然而,在现代浏览器环境下,很多原来性能障碍都解决了,新的障碍又特么出来了

1)所有现代浏览器(IE9+),都有“预加载器”其实本质是“预下载”,在渲染DOM的同时把所有网页资源都下载好(JS、css、img等等)

2)关于并行下载呢,经测试,所有浏览器都可以同时下载10-20个资源文件,比原来2-6个多的多了,作为现代的JS加载器没必要考虑并行下载的问题

3)现在大部分码农都只是不用或者只用JQuery里面的一部分了吧,追求性能是必须的,当然随着项目越来越庞大,引入Angular,Ember之类的巨型框架也是没办法,下载慢到不担心,关键执行慢卡死在那里结果用户一直白屏

4)defer="defer"已被所有浏览器支持,只是只适用于外部JS,内联JS只在IE有效,这正是本文要解决的问题

5)因为JS MVC框架的模板问题,很多JS代码不得不写在HTML里以获得服务器模板引擎的渲染(当然如果能让render支持渲染需要的JS文件那是最好的)

 

好,show code

因为Riot.js,Angular.js这类框架对JS文件的加载时间段有很高的要求,所以用他们做demo再合适不过

1)给所有的外部JS加上 defer="defer"

    <script src='riot.min.js' defer='defer'></script>
    <script src='todo.js' defer='defer'></script>

2)给内联JS封装一个DOMContentLoaded,现代浏览器都提供了100%这个事件,然而并没有封装。。。。不像window.load那样直接可用,把下面的代码放入一个外部JS,并且不要使用 defer="defer"

这个主要目的是做一个队列管理

//保存domReady的事件队列
eventQueue = [];
 
//判断DOM是否加载完毕
isReady = false;
 
//判断DOMReady是否绑定
isBind = false;
 
/*执行domReady()
 *
 *@param    {function}
 *@execute  将事件处理程序压入事件队列,并绑定DOMContentLoaded
 *          如果DOM加载已经完成,则立即执行
 *@caller
 */
function domReady(fn){
    if (isReady) {
        fn.call(window);
    }
    else{
        eventQueue.push(fn);
    };
 
    bindReady();
};
 
/*domReady事件绑定
 *
 *@param    null
 *@execute  浏览器通过addEvListener绑定DOMContentLoaded,包括ie9+
 *@caller   domReady()
 */
function bindReady(){
    if (isReady) return;
    if (isBind) return;
    isBind = true;
        document.addEventListener('DOMContentLoaded',execFn,false);
};
 
/*执行事件队列
 *
 *@param    null
 *@execute  循环执行队列中的事件处理程序
 *@caller   bindReady()
 */
function execFn(){
    if (!isReady) {
        isReady = true;
        for (var i = 0; i < eventQueue.length; i++) {
            eventQueue[i].call(window);
        };
        eventQueue = [];
    };
};

如果非要支持IE6-8,那这里有个完整的demo http://dengo.org/archives/1037

亲测表示这个比Jquery的document.ready快!

其实这个时候JQuery的document.ready是用不了的,因为JQ还没被加载呢,除非你不对JQ异步加载,这样异步加载的意义就不大了

3)对于内联JS代码,以后统一用 domReady(function(){  //   });包裹

//内联JS 1
domReady(function(){
      //这是个例子哈
      riot.mount('todo', {
      title: 'I want to behave!',
      items: [
        { title: 'Avoid excessive coffeine', done: true },
        { title: 'Hidden item', hidden: true },
        { title: 'Be less provocative' },
        { title: 'Be nice to people' }
      ]
    })
});

这个domReady()可以调用多次,直接用就行


如果不需要统一管理DCL事件的话
document.addEventListener("DOMContentLoaded", function(event) {
// code
});

注意哦,这个不支持IE6-8的哈,另外呢,直接绑定一个个独立的DOMCL事件的话,可能存在一个重复绑定的问题,可能导致执行循序出错,上面的那段代码就是为了做一个队列管理


好吧我科普一下,这个DOMContentLoaded就是在DOM结构加载完就触发(就是chrome developer tools -> TimeLine 里的那根蓝线),而window.load是在整个页面全部加载完时触发(就是chrome developer tools -> TimeLine 里的那根红线,在用户看来就是小菊花不转的时候)

 

注1:如果需要操作图片什么的,需要等待所有资源下载完毕,直接用window.load = (function(){ // });,不过只能用一次,解决方案网上很多

注2:如果你的网页只需要支持IE6-11,那么直接给内联JS加 defer="defer",W3C标准是支持内联JS的,只是FF和Chrome都没遵守。。。所以本文实际是在给FF和Chrome擦屁股。。。


好这样就解决了所有问题,结论就是,如果你需要引入很多庞大的库,又不想过于影响页面的首次加载速度(白屏时间),这个方案适合所有网站,嗯,用吧。

本文按cc-署名-以相同方式共享传播

你可能感兴趣的:(js,异步加载)