JavaScript异步加载脚本

淮城一只猫 奇技淫巧
阅读量 0 评论量 0

Cap1 前言

在Nuxt.js打包时候发现vendors文件实在是太大,看了官方文档发现新版又不支持老的Api,所以寻找一上午也没找到很好的方案,目前把一些类库文件抽取出来直接CDN加载就行,但原本以为这是很简单事情,可是中间又遇到不少问题,例如脚本和脚本直接的依赖关系等。

Cap2 HTML5

其实在HTML5也有相关的标签属性可以实现:

<script src="//code.jquery.com/jquery-1.11.0.min.js" async></script>

async 属性规定一旦脚本可用,则会异步执行。

注释:async 属性仅适用于外部脚本(只有在使用 src 属性时)。

注释:有多种执行外部脚本的方法:

  • 如果 async="async":脚本相对于页面的其余部分异步地执行(当页面继续进行解析时,脚本将被执行)
  • 如果不使用 async 且 defer="defer":脚本将在页面完成解析时执行
  • 如果既不使用 async 也不使用 defer:在浏览器继续解析页面之前,立即读取并执行脚本

Cap3 编程方式

(function() {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.async = true;
    script.src = "//code.jquery.com/jquery-1.11.0.min.js";
    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(script);
})();

这种动态插入脚本的方法基本上主流写法。

Cap4 支持回调函数的编程方式

如果需要在插入脚本执行代码执行下一步操作:

function loadScript(url, callback) {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.async = true;
    if (script.readyState) {
        script.onreadystatechange = function () {
            if (script.readyState == "loaded" || script.readyState == "complete") {
                script.onreadystatechange = null;
                if (callback && typeof callback === "function") {
                    callback();
                }
            }
        };
    } else {
        script.onload = function () {
            if (callback && typeof callback === "function") {
                callback();
            }
        };
    }
    script.src = url;
    (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(script);
}

理论上这样写法满足大量的需求了,但是在我这里遇到一个问题:

如果一层一层回调下去,岂不是难以解读。所以还需要一个更优雅的写法:

Cap5 支持回调函数的ES8风格编程方式

async function loadScripts (scripts) {
    function get (src) {
        return new Promise(function (resolve, reject) {
            var el = document.createElement("script");
            el.async = true;
            el.addEventListener("load", function () {
                resolve(src);
            }, false);
            el.addEventListener("error", function () {
                reject(src);
            }, false);
            el.src = src;
            (document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(el);
        });
    }
 
    const myPromises = scripts.map(async function (script, index) {
        return await get(script);
    });
 
    return await Promise.all(myPromises);
}

到上面脚本来看,无论哪方面都可以满足了:

loadScripts([
    "https://static.zinoui.com/1.5/compiled/zino.svg.min.js",
    "https://static.zinoui.com/libs/jquery/jquery.min.js"
]).then(function () {
    return loadScripts(["https://static.zinoui.com/1.5/compiled/zino.chart.min.js"]);
}).then(function () {
    $("#chart").zinoChart(settings);
});
喵~