使用ES6特性优雅按顺序同步加载JS文件

前言


随着项目功能越来越完善,js文件多了又多,如何通过一个init.js去管理项目所需要的js(重点:解决js文件顺序加载的问题),本文章为你揭秘。

踩坑


当前的项目状况

a.html使用的js部分

<script type="text/javascript" src="../jquery.js"></script><!--jquery类库-->

<script type="text/javascript" src="../jquery-x1.js"></script><!--某jquery插件-->
<script type="text/javascript" src="../jquery-x2.js"></script>
<script type="text/javascript" src="../jquery-x3.js"></script>

<script type="text/javascript" src="../app-core.js"></script><!--项目框架类库-->
<script type="text/javascript" src="../app-api.js"></script><!--项目框架类库-->

<script type="text/javascript" src="../page-a-control.js"></script><!--当前页面的控制-->

b.html使用的js部分

<script type="text/javascript" src="../jquery.js"></script><!--jquery类库-->

<script type="text/javascript" src="../jquery-x1.js"></script><!--某jquery插件-->
<script type="text/javascript" src="../jquery-x2.js"></script>
<script type="text/javascript" src="../jquery-x3.js"></script>
...
<script type="text/javascript" src="../app-core.js"></script><!--项目框架类库-->
<script type="text/javascript" src="../app-api.js"></script><!--项目框架类库-->
...
<script type="text/javascript" src="../page-b-control.js"></script><!--当前页面的控制-->

通过观察可以发现a.html和b.html的区别就是,除了最后的一个用于控制当前页面逻辑的js文件之外,其他的大体都一样,我这边只是举了一个简单的例子,真实项目的复杂度还是需要根据项目而定。

为了解决这个问题,我想了以下方案:

  1. 合并页面中相同的js生成一个js文件。后来考虑了一下项目的复杂度(合并复杂度,以及代码冗余),觉得这不是解决问题的根本。
  2. 使用module体系,这样改动的复杂度更高。
  3. 使用一个init.js,去动态创建script标签加载页面所需要用的js。

最后还是决定使用init.js去动态加载js来控制。

出坑之路


使用init.js解决该问题,瞬间想到以下思路,看代码:

init.js

var initlist=[
   "../jquerymin.js",
   "../jquery.x1.js",
   "../jquery.x2.js",
   "../jquery.x3.js",
   "../core-1.js",
   "../model-api.js"
];
function loadjs (jsFiles) {
    var ele;
    for(var init_key in jsFiles){
        ele = document.createElement('script');
        ele.src =initlist[init_key];//+'?r=' + Math.random() * 999999 加上随机数可以防止缓存
        document.getElementsByTagName("head")[0].appendChild(ele);
    }
}
function initMain(jsFiles,func){
    loadjs(initlist);//1,加载框架
    loadjs(jsFiles);//2,根据每个页面的特性动态加载制定的js
    func();
}

使用方式:

<script type="text/javascript" src="../init.js"></script>
<script type="text/javascript">
    initMain(["../page-a-control.js"],function(){
        console.log("加载完成");
    });
</script>

然而当我写完这个代码感觉大功告成的时候,测试总是报类未定义,排错时发现有时候可以运行成功,有时候却失败。很费解,创建出来的script标签都是有顺序的,然而却出现类未定义。后来度娘了一下,才知道这种方式创建出来的script标签,加载js的方式都是异步的,所以会出现以上问题。并且度娘上的解决方式我找了一大堆都是使用ajax方式,很多也很扯,根本不能解决。ajax方式的确可以,但是我们的网页需要能够离线使用就是直接使用file方式就能成功打开,ajax方式并不符合要求,并且我个人也不是太倾向于用ajax方式。

so,以上方案,pass


阅读了大量文档后:
最后解决方案就是创建script对象,然后在onload回调中去加载下一个js,以达到同步加载效果。结合最近学习的es6/es7语法,完美解决问题,代码如下:

init.js

以下js错误,请看后边的修正方法(2017.6.8)

错误:发现Promise.all方法有问题,不是按顺序执行的,请看后边的修正方案,更新于2017.6.8

var initlist=[
   "../jquerymin.js",
   "../jquery.x1.js",
   "../jquery.x2.js",
   "../jquery.x3.js",
   "../core-1.js",
   "../model-api.js"
];//需要加载的列表,这里可以多配置几个,根据不同需求加载不同的列表

//错误:发现Promise.all方法有问题,不是按顺序执行的,斜线内为老内容,更新于2017.6.8
var loadjs=(key)=>new Promise((resolve, reject)=>{
    var script = document.createElement('script');;
    script.src =key ;//+'?r=' + Math.random() * 9999
    script.onload=()=>{
        resolve();
    };
    document.getElementsByTagName("head")[0].appendChild(script);
});
//es6语法
function initMain(jsFiles,func){
    var tasks=[];
    initlist=initlist.concat(jsFiles);
    for(var init_key in initlist){
            tasks.push(loadjs(initlist[init_key]));
        }
    Promise.all(tasks).then(()=>{
        func();
    });
}

//es7语法,浏览器兼容不是太好,慎用
async function initMain(jsFiles,func){
    initlist=initlist.concat(jsFiles);
    for(var init_key in initlist){
        await loadjs(initlist[init_key]);
    }
    await func();
}


使用方式:

<script type="text/javascript" src="../init.js"></script>
<script type="text/javascript">
    initMain(["../page-a-control.js"],()=>{
        console.log("加载完成");
        $(()=>{ //使用init.js,jquery需要在回调里边使用
            alert("加载完成");
        })
    });
</script>

新的修正方法

init.js

var initlist=[
   "../jquerymin.js",
   "../jquery.x1.js",
   "../jquery.x2.js",
   "../jquery.x3.js",
   "../core-1.js",
   "../model-api.js"
];//需要加载的列表,这里可以多配置几个,根据不同需求加载不同的列表
//该方法es5支持
function loadjs(jss,func) {
        if(jss.length<=0){
            console.log(jss)
            func();
            return;
        }
        var key = jss.shift();
        var script = document.createElement('script');;
        script.src =key ;//+'?r=' + Math.random() * 9999
        script.onload=function(){
            loadjs(jss,func)
        };
        document.getElementsByTagName("head")[0].appendChild(script);
    }
//该方法es6支持
function loadjs(jss,func) {
        var p=function(key) {
           return new Promise(function (resolve, reject) {
                var script = document.createElement('script');
                script.src = key;//+'?r=' + Math.random() * 9999
                script.onload = function () {
                    console.log('load--ok:' + key);
                    resolve();
                };
               script.onerror=function () {
                   reject(new Error(key+"--加载失败"));
               };
                document.getElementsByTagName("head")[0].appendChild(script);
            });
        };
        if(jss.length<=0){
            console.log(jss);
            func();
            return;
        };
        var key = jss.shift();
        p(key).then(function(){
            loadjs(jss,func);
        }).catch(function(e){
            console.log(e);
        });
    }
function initMain(jsFiles,func){
        var tasks=[];
        initlist=initlist.concat(jsFiles);
        loadjs(initlist,func)
    }

使用方式:

<script type="text/javascript" src="../init.js"></script>
<script type="text/javascript">
    initMain(["../page-a-control.js"],()=>{
        console.log("加载完成");
        $(()=>{ //使用init.js,jquery需要在回调里边使用
            alert("加载完成");
        })
    });
</script>

最后成功的使用了init.js对项目所使用的js进行了管理,使项目结构变得清晰。

重点总结


onload事件的运用,除了body有此事件,常见的img,script标签 等都具有该事件

Promise对象极大的提高了异步事件的灵活性和代码的可阅读性。


demo下载

http://download.csdn.net/download/zhaoyu813113552/9804261

发表评论