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

时间:2016-08-08 22:50:30

标签: javascript html typescript load-order

我有一个打字稿应用程序,可动态添加指向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文件中静态定义脚本标记时的排序方式)?

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

3 个答案:

答案 0 :(得分:9)

有一些方法可以克服这一要求。现在我可以建议如下:

  1. 使用库注入依赖项(AMD或CommonJS模块)
  2. 以编程方式创建script代码并设置属性async = false
  3. 以编程方式创建script标记并设置回调onload,以便在脚本异步加载时作出反应。默认情况下设置属性async = true。 (见下面的例子)
  4. 如果您不喜欢以前的选项,并且您可以修改要注入的脚本,那么在脚本末尾添加一行,其中object / array可以跟踪脚本已加载。
  5. 作为最后一个资源,您可以将脚本作为文本获取,使用所需顺序的脚本构建string(将每个文本脚本包装在IIFE内)并最终执行文本 - 脚本通过可怕而危险的eval()
  6. 最糟糕的选择是使用setInterval检查脚本是否已执行。我添加了最后一个选项只是因为我已经看到了一些使用这种糟糕技术的解决方案。
  7. 如果你在一个大项目中工作,我会推荐第一个选项。但是如果你有一个小项目,你可以选择第三个选项。为什么呢?

    让我们考虑在第二个选项中,通过设置 属性async = false将导致浏览器阻止呈现,直到脚本执行完毕(不良做法)。

    我想推荐一个关于脚本加载器的阅读材料: Deep dive into the murky waters of script loading ,半小时值得花钱!

    我写了一个用于管理脚本注入的小模块的示例,这是背后的基本思想:

    let _scripts = [
      'path/to/script1.js',
      'path/to/script2.js',
      'path/to/script3.js'
    ];
    
    function createScriptTag() {
      // gets the first script in the list
      let script = _scripts.shift();
      // all scripts were loaded
      if (!script) return;
      let js = document.createElement('script');
      js.type = 'text/javascript';
      js.src = script;
      js.onload = (event) => {
        // loads the next script
        createScriptTag();
      };
      let s = document.getElementsByTagName('script')[0];
      s.parentNode.insertBefore(js, s);
    }
    

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

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

    • addScript:收到网址的网址或Array个网址。
    • load:按特定顺序运行加载脚本的任务。
    • reset:清除脚本数组,或取消脚本加载。
    • afterLoad:在加载每个脚本后执行回调。
    • onComplete:在加载所有脚本后执行回调。

    我喜欢流畅的界面方法链接方式,然后我以这种方式构建模块:

      jsuLoader
        .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()回调。

答案 1 :(得分: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);
  });
}

答案 2 :(得分: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
});