动态加载的javascript库何时可用?

When does a dynamically loaded JavaScript library become available?

我编写了javascript库来使用filesaver.js及其相关库。但是,我不希望每次有人想使用我的库时都加载filesaver.js。我不想强迫他们用script标记自己加载所有与filesaver相关的javascript库(或者甚至加载我的一个这样做的库)。

相反,我更喜欢这样的东西。当他们调用我的createImage函数时,它首先执行以下操作:

1
2
3
4
5
6
7
8
9
10
function createImage(image, name) {
  if (typeof(saveAs) !== 'function') {
    var element = document.createElement('script');
    element.async = false;
    element.src = 'FileSaver.js';
    element.type = 'text/javascript';
    (document.getElementsByTagName('head')[0]||document.body).appendChild(element);
  }
  // now do the saveImage code
}

问题是,在上述之后,仍然没有定义saveAs函数。只有在我的createImage完成之后,才最终定义了saveAs函数。


整体解决方案是使用模块系统。AMD可能是最常用的浏览器异步代码加载系统。AMD只是一个规范,但是像require.js之类的东西是使用AMD模块的非常流行的工具。

您可以定义模块之间的依赖关系,如果需要的话,require.js将获取它们。一般的想法是模仿其他语言的导入/命名空间功能(如Java、C语言或Python)。代码共享"我想是这个词吗?

只需将所有代码放入一个回调函数中,该函数在加载依赖项后运行,这样就可以确保存在所需的对象和方法。

更新2015

只是一个附录。虽然上面的信息仍然正确,但是前端代码管理正在迅速向Webpack和Browserify等解决方案发展,它们捆绑并连接任何模块类型的代码,并且都具有动态代码加载功能(Webpack称之为代码拆分)。再加上依赖管理的NPM指数增长,AMD的相关性开始降低。


好的,您需要做的是监听脚本以完成加载。不幸的是,对于IE<7的代码有一些错误。

这是moootools Asset.javascript加载脚本并在其完成时调用回调的方式:

1
2
3
4
5
6
7
8
9
10
11
12
var loadScript = function (source, properties) {
    properties || (properties = {});
    var script = document.createElement('script');
    script.async = true;
    script.src = source;
    script.type = 'text/javascript';
    var doc = properties.document || document, load = properties.onload || properties.onLoad;
    return delete properties.onload, delete properties.onLoad, delete properties.document,
    load && (script.addEventListener ? script.addEventListener("load", load) : script.attachEvent("readystatechange", function() {
        ["loaded","complete" ].indexOf(this.readyState) >= 0 && load.call(this);
    })), script.set(properties).appendChild(doc.head);
}

现在在loadImage中,可以按如下方式加载文件库:

1
2
3
4
5
6
7
8
9
10
11
function createImage(image, name) {
  function createImg() {
      // now do the saveImage code
  }
  if (typeof(saveAs) !== 'function') {
     loadScript("FileSaver.js", {onLoad: createImg});//load library
  }
  else {
     createImg();
  }
}

应该适用于大多数浏览器。


最简单的答案是将代码放在您创建的script标记的onload处理程序中:

1
2
3
4
5
6
7
8
  var firstScript = document.getElementsByTagName('script')[0],
      js = document.createElement('script');
  js.src = 'https://cdnjs.cloudflare.com/ajax/libs/Snowstorm/20131208/snowstorm-min.js';
  js.onload = function () {
    // do stuff with your dynamically loaded script
    snowStorm.snowColor = '#99ccff';
  };
  firstScript.parentNode.insertBefore(js, firstScript);

以这种方式动态加载脚本是由Facebook完成的。


所以我同意AMD的评论(不能将代码阻塞放入评论MEH…)

以下是我为filesaver.js所做的工作

首先在我的RequireJS config/main.js中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(function() {
    // REMEMBER TO DUPLICATE CHANGES IN GRUNTFILE.JS
    requirejs.config({
        paths: {
           "jquery":"PATH/jquery.min", // NO .js
           "lib.filesaver" :"PATH/FileSaver", // NO .js
           "shim.blob" :"PATH/Blob" // NO .js
        },
        shim: {
           "lib.filesaver": {deps: ["shim.blob"]}
        }
    });

    define([
       "jquery"
    ], function(
        $
        ) {
            $(document).ready(function() {
                // start up code...
            });
        return {};
        });
})();

然后我将blob.js/jquery和filersaver放在正确的位置

我还为IE10之前的版本创建了一个IEShim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
define([], function () {
    /**
     * @class IEshims
     * container for static IE shim functions
     */

    var IEShims = {
        /**
         * saveFile, pops up a built in javascript file as a download
         * @param {String} filename, eg doc.csv
         * @param {String} filecontent eg"this","is","csv"
         */

        saveAs: function (filename, filecontent, mimetype ) {
            var w = window.open();
            var doc = w.document;
            doc.open( mimetype,'replace');
            doc.charset ="utf-8";
            doc.write(filecontent);
            doc.close();
            doc.execCommand("SaveAs", null, filename);
        }
    };
    return IEShims;
});

最后,当我想使用filesaver时,需要它(以及坏浏览器的ieshim)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
define([
"lib.filesaver",
"IEShims"
],
function (
    FileSaver, // it's empty, see saveAs global var
    IEShims
    ) {
...
    var fileName ="helloworld.txt";
    var fileContents ="Me haz file contents, K Thx Bye";
    var mimeType ="text/plain";
    if(saveAs) {
        var blob = new Blob(
            [fileContents],
            {type: mimeType +";charset=" + document.characterSet}
        );
        saveAs(blob, fileName);
    } else {
        IEShims.saveAs(fileName, fileContents,mimeType );
    }
    ...
};

使用head.js:http://headjs.com/

它将按需加载脚本。