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

Javascript 动态添加的脚本标记的加载顺序,javascript,html,typescript,load-order,Javascript,Html,Typescript,Load Order,我有一个typescript应用程序,可以动态添加指向JS文件的脚本标记。由于一些限制,我无法在html文件中静态定义这些脚本标记,因此我通过typescript动态添加它们,如下所示: for (let src of jsFiles) { let sTag = document.createElement('script'); sTag.type = 'text/javascript'; sTag.src = src; sTag.defer = true; document

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


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

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

  • 使用库注入依赖项(AMD或CommonJS模块)
    只需使用。ES2015:
    导入
    /
    导出
    ,或CommonJS:
    要求()
  • 以编程方式创建
    script
    标记,并设置回调
    onload
    ,以便在异步加载脚本时做出反应。默认设置属性
    async=true
  • 如果允许修改要注入的脚本,则在脚本末尾添加一行,其中包含跟踪已加载脚本的
    对象
    数组
  • 您可以将脚本作为文本获取(
    XMLHttpRequest
    ),然后按照所需顺序使用脚本构建一个
    字符串
    ,最后通过
  • 还有一个不太推荐但经常使用的选项,设置一个
    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
      :在加载所有脚本后执行回调
    我喜欢流畅的界面方法链接技术,因此我以这种方式构建了模块:

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

    我希望避免使用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);
      });
    }
    
    /**
    *实用程序:在加载脚本时解析
    */
    函数包括(url:string){
    返回新承诺((解决、拒绝)=>{
    var script=document.createElement('script');
    script.type='text/javascript';
    script.src=url;
    script.onload=函数(){
    解析({script});
    };
    document.getElementsByTagName('head')[0].appendChild(脚本);
    });
    }
    
    对于使用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
    });
    

    在普通的旧JS中,我会通过使用RequireJS使依赖项显式化来解决这个问题。然后每个脚本的主代码总是被包装,它们总是按照我想要的顺序执行,而不管上面的执行顺序如何。类似的方法也适用于你。如果项目很小,并且总是很小,那么可以在开始时添加一个非延迟脚本来管理执行顺序。然后,任何需要延迟执行的脚本都可以包装在一个函数中,该函数被传递到该控制脚本中。对defer属性有一些有用的注释。我相信有些库允许您异步下载文件,然后按顺序执行。请参阅RequireJS等。它们的性能比等待onload甚至开始下载下一个脚本要好得多。在2017年年中推荐RequireJS不是一个好建议。RequireJS已经过时,不再受支持——已经有几年了。查看Webpack、JSPM和Rollup.js作为更好的替代方案。或者script.js和loadjs在2019年这仍然是一种有效的方法,还是有更好的方法?引用的文章是2013年的,这在互联网上是很古老的。嗨@JoAMoS,不是,这个解决方案只适用于在浏览器中运行的旧项目。对于在TypeScript下运行的项目,Web