繁体   English   中英

动态添加的脚本标签的加载顺序

[英]Load ordering of dynamically added script tags

我有一个打字稿应用程序,可以动态添加指向 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 回调来解决这个问题,因为我注意到我的应用程序的性能下降。 我的第二个脚本文件非常大,我假设这导致了降级。

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

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

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

以编程方式创建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 Interface方法链技术,所以我以这种方式构建了模块:

  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"做同样的"script3.js" ,在所有脚本加载完成后, onComplete()回调被执行。

我想避免使用 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);
  });
}

对于使用 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
});

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM