13

我有一个打字稿应用程序,可以动态添加指向 JS 文件的脚本标签。由于一些限制,我不能在 html 文件中静态定义这些脚本标签,所以我通过 typescript 动态添加它们,如下所示:

for (let src of jsFiles) {
  let sTag = document.createElement('script');
  sTag.type = 'text/javascript';
  sTag.src = src;
  sTag.defer = true;
  document.body.appendChild(script);
}

现在,我注意到,当我动态添加脚本标签时,它们似乎并不能保证它们的加载顺序。不幸的是,该jsFiles数组具有相互依赖的脚本。因此,数组中的第二个脚本只能在第一个脚本完全加载后才能加载。第二个脚本引用了第一个脚本中定义的函数。有没有一种方法可以在动态添加脚本时指定脚本的排序和执行顺序(类似于在 html 文件中静态定义脚本标签时的排序方式)?

PS 我想避免使用 onload 回调来解决这个问题,因为我注意到我的应用程序的性能下降。我的第二个脚本文件非常大,我假设这导致了降级。

4

3 回答 3

18

我可以提到一些替代方案来克服这一要求:

  1. 使用库注入依赖项(AMD 或 CommonJS 模块)
    只需使用模块。ES2015: import/export或 CommonJS: require().
  2. 以编程方式创建script标签并设置回调onload,以便在异步加载脚本时做出反应。该属性async = true是默认设置的。
  3. 如果允许您修改要注入的脚本,请在脚本末尾添加一行,使用objectorarray来跟踪已加载的脚本。
  4. 您可以将脚本作为文本 ( ) 获取,然后以所需的顺序使用脚本XMLHttpRequest构建一个,最后,通过stringeval()
  5. 而不太推荐但经常使用的选项,设置 asetInterval以检查脚本是否已执行。

我建议选择第一个选项。但出于学术目的,我将说明第二种选择:

以编程方式创建script标签并设置回调onload,以便在异步加载脚本时做出反应。

我想推荐一本关于脚本加载器的读物:深入了解脚本加载的浑水,半小时值得!

下面的例子是一个管理脚本注入的小模块,这是它背后的基本思想:

let _scriptsToLoad = [
  'path/to/script1.js',
  'path/to/script2.js',
  'path/to/script3.js'
];

function createScriptElement() {
  // gets the first script in the list
  let script = _scriptsToLoad.shift();
  // all scripts were loaded
  if (!script) return;
  let js = document.createElement('script');
  js.type = 'text/javascript';
  js.src = script;
  js.onload = onScriptLoaded;
  let s = document.getElementsByTagName('script')[0];
  s.parentNode.insertBefore(js, s);
}

function onScriptLoaded(event) {
  // loads the next script
  createScriptElement();
};

在这个plunker 中,您可以按特定顺序异步测试脚本注入:

主要思想是创建一个 API,允许您通过公开以下方法与要注入的脚本进行交互:

  • addScript:接收要加载的每个脚本的 URL 或 URL 列表。
  • load:运行任务以指定顺序加载脚本。
  • reset:清除脚本数组,或取消脚本加载。
  • afterLoad: 每个脚本加载后执行的回调。
  • onComplete: 加载所有脚本后执行的回调。

我喜欢Fluent 接口方法链接技术,所以我以这种方式构建了模块:

  scriptsLoader
    .reset()
    .addScript("script1.js")
    .addScript(["script2.js", "script3.js"])
    .afterLoad((src) => console.warn("> loaded from jsuLoader:", src))
    .onComplete(() => console.info("* ALL SCRIPTS LOADED *"))
    .load();

在上面的代码中,我们首先加载"script1.js"文件,并执行afterLoad()回调,接下来,对所有脚本执行相同的操作,"script2.js"并且"script3.js"在加载完所有脚本后,onComplete()执行回调。

于 2016-08-09T01:33:56.050 回答
1

我想避免使用 onload 回调来解决这个问题,因为我注意到我的应用程序的性能下降。

如果您想要订购文件......您需要等待每个文件的 onload。没有办法解决它。

这是我曾经写过的一个实用函数:

/**
 * Utility : Resolves when a script has been loaded
 */
function include(url: string) {
  return new Promise<{ script: HTMLScriptElement }>((resolve, reject) => {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;

    script.onload = function() {
      resolve({ script });
    };

    document.getElementsByTagName('head')[0].appendChild(script);
  });
}
于 2016-08-09T03:52:04.907 回答
0

对于使用 jQuery 的任何人,我都改进了 @adeneo 脚本,因此它将按指定顺序加载所有脚本。它不进行链式加载,因此非常快,但如果您想要更快,请更改 50 毫秒的等待时间。

$.getMultiScripts = function(arr, path) {

    function executeInOrder(scr, code, resolve) {
        // if its the first script that should be executed
        if (scr == arr[0]) {
            arr.shift();
            eval(code);
            resolve();
            console.log('executed', scr);
        } else {
            // waiting
            setTimeout(function(){
                executeInOrder(scr, code, resolve);
            }, 50);
        }
    }

    var _arr = $.map(arr, function(scr) {

        return new Promise((resolve) => {
            jQuery.ajax({
                type: "GET",
                url: (path || '') + scr,
                dataType: "text",
                success: function(code) {
                    console.log('loaded  ', scr);
                    executeInOrder(scr, code, resolve);
                },
                cache: true
            });
        });

    });
        
    _arr.push($.Deferred(function( deferred ){
        $( deferred.resolve );
    }));
        
    return $.when.apply($, _arr);
}

如何使用:

var script_arr = [
    'myscript1.js', 
    'myscript2.js', 
    'myscript3.js'
];

$.getMultiScripts(script_arr, '/mypath/').done(function() {
    // all scripts loaded
});
于 2020-11-05T15:43:27.223 回答