简体   繁体   English

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

[英]Load ordering of dynamically added script tags

I have a typescript application that dynamically adds script tags that point to JS files.我有一个打字稿应用程序,可以动态添加指向 JS 文件的脚本标签。 Due to some restrictions I cant have these script tags statically defined in a html file, so I add them dynamically through typescript like this:由于某些限制,我无法在 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);
}

Now, I am noticing that, when I add script tags dynamically, their doesn't seem to be a guarantee in the order in which they are loaded.现在,我注意到,当我动态添加脚本标签时,它们似乎并不能保证它们的加载顺序。 Unfortunately, the jsFiles array has scripts that are dependent on each other.不幸的是, jsFiles数组具有相互依赖的脚本。 So, the 2nd script in the array can only be loaded after the first one is fully loaded.因此,数组中的第二个脚本只能在第一个脚本完全加载后加载。 The second script references a function that is defined in the first one.第二个脚本引用了在第一个脚本中定义的函数。 Is there a way I can specify the order in which scripts are ordered and executed when adding them dynamically (similar to how ordering is done when you statically define script tags in a html file)?有没有一种方法可以在动态添加脚本时指定脚本的排序和执行顺序(类似于在 html 文件中静态定义脚本标签时的排序方式)?

PS I would like to avoid using the onload callback to solve this issue since I noticed a performance degradation with my application. PS 我想避免使用 onload 回调来解决这个问题,因为我注意到我的应用程序的性能下降。 My second script file is very large and I am assuming that caused the degradation.我的第二个脚本文件非常大,我假设这导致了降级。

I can mention some alternatives to overcome that requirement:我可以提到一些替代方案来克服该要求:

  1. Use a library to inject dependencies (AMD or CommonJS modules)使用库注入依赖项(AMD 或 CommonJS 模块)
    Just use modules .只需使用模块 ES2015: import / export , or CommonJS: require() . ES2015: import / export ,或 CommonJS: require()
  2. Create the script tag programmatically and set the callback onload in order to react when the script has been loaded asynchronously.以编程方式创建script标记并设置回调onload以便在异步加载脚本时做出反应。 The attribute async = true is set by default.属性async = true是默认设置的。
  3. If you are allowed to modify the scripts to inject, then add a line at the end of scripts with an object or array that keeps track of the scripts already loaded.如果允许修改要注入的脚本,则在脚本末尾添加一行,其中包含一个objectarray ,用于跟踪已加载的脚本。
  4. You can fetch the scripts as text ( XMLHttpRequest ), then, build a string with the scripts in the required order and, finally, execute the text-script via eval()您可以将脚本作为文本( XMLHttpRequest )获取,然后按照所需的顺序使用脚本构建一个string ,最后通过eval()执行文本脚本
  5. And the less recommended option but frequently used, set a setInterval to check if the script was already executed.而不太推荐但经常使用的选项,设置一个setInterval来检查脚本是否已经执行。

I recommend going for the first option.我建议选择第一个选项。 But for academic purposes, I'm going to illustrate the second option:但出于学术目的,我将说明第二种选择:

Create the script tag programmatically and set the callback onload in order to react when the script has been loaded asynchronously.以编程方式创建script标记并设置回调onload以便在异步加载脚本时做出反应。

I want to recommend a reading about script loaders: Deep dive into the murky waters of script loading , half hour worth spending!我想推荐一本关于脚本加载器的读物: 深入了解脚本加载的阴暗面,值得花半个小时!

The following example is a small module to manage scripts injection, and this is the basic idea behind it:下面的例子是一个管理脚本注入的小模块,这是它背后的基本思想:

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();
};

In this plunker you can test the injection of scripts asynchronously in a specific order:在此plunker 中,您可以按特定顺序异步测试脚本注入:

The main idea was to create an API that allows you interact with the scripts to inject, by exposing the following methods:主要思想是创建一个 API,允许您通过公开以下方法与要注入的脚本进行交互:

  • addScript : receive an URL or a list of URLs for each script to be loaded. addScript :接收要加载的每个脚本的 URL 或 URL 列表。
  • load : Run the task to load scripts in the specified order. load :运行任务以按指定顺序加载脚本。
  • reset : Clear the array of scripts, or cancels the load of scripts. reset :清除脚本数组,或取消脚本加载。
  • afterLoad : Callback executed after every script has been loaded. afterLoad :在每个脚本加载后执行的回调。
  • onComplete : Callback executed after all scripts have been loaded. onComplete :在所有脚本加载后执行的回调。

I like Fluent Interface or method chaining technique, so I built the module that way:我喜欢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();

In the code above, we load first the "script1.js" file, and execute the afterLoad() callback, next, do the same with "script2.js" and "script3.js" and after all scripts has been loaded, the onComplete() callback is executed.在上面的代码中,我们首先加载"script1.js"文件,并执行afterLoad()回调,接下来,对"script2.js""script3.js"做同样的"script3.js" ,在所有脚本加载完成后, onComplete()回调被执行。

I would like to avoid using the onload callback to solve this issue since I noticed a performance degradation with my application.我想避免使用 onload 回调来解决这个问题,因为我注意到我的应用程序性能下降。

If you want the files ordered ... you need to wait for onload on each file.如果您想要订购文件......您需要等待每个文件的加载。 No way around it.没办法。

Here is a utility function I wrote once:这是我曾经写过的一个实用函数:

/**
 * 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);
  });
}

For anyone who is using jQuery I have improved @adeneo script so it will load all scripts in the specified order.对于使用 jQuery 的任何人,我都改进了 @adeneo 脚本,因此它将按指定顺序加载所有脚本。 It doesn't do chain loading, so it's very fast, but if you want even faster, change the 50 ms wait time.它不进行链式加载,因此速度非常快,但如果您想要更快,请更改 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);
}

How to use:如何使用:

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