加载动态添加的脚本标签的顺序 [英] Load ordering of dynamically added script tags

查看:192
本文介绍了加载动态添加的脚本标签的顺序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个打字脚本应用程序,它动态地添加指向JS文件的脚本标记。由于一些限制,我无法在HTML文件中静态定义这些脚本标签,所以我通过类型脚本动态添加它们:

  for(让jsFiles的src){
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模块)
  2. 以编程方式创建脚本标记并设置属性 async = false

  3. 以编程方式创建脚本标记并设置回调 onload ,以便在脚本异步加载时作出反应。属性 async = true 默认设置。 (请参阅下面的示例)
  4. 如果您不喜欢以前的选项,并且允许修改要注入的脚本,则可以在脚本结尾添加一行以对象 / 数组,用于跟踪所加载的脚本。

  5. 作为最后一个资源,可以将脚本作为文本获取,使用脚本以所需顺序构建字符串(将每个文本脚本包装在 IIFE ),最后通过可怕和危险的 eval() / code>。

  6. 最糟糕的选择是使用 setInterval 来检查脚本是否被执行。我添加了最后一个选项,因为我已经看到了一些使用这种不好的技术的解决方案。

如果你在一个大项目中工作,会建议第一个选项。但是如果你有一个小项目,你可以选择第三个选项。为什么?

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



我想推荐关于脚本加载器的阅读: 深入潜入脚本加载的小水域 ,花费半个小时!



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

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

函数createScriptTag(){
//获取列表中的第一个脚本
let script = _scripts.shift();
//所有脚本都被加载
if(!script)return;
let js = document.createElement('script');
js.type ='text / javascript';
js.src = script;
js.onload =(event)=> {
//载入下一个脚本
createScriptTag();
};
let s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(js,s);

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



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


  • addScript :接收URL的url或数组的脚本url。

  • load code>:以特定顺序运行加载脚本的任务。
  • reset :清除脚本数组,或者取消脚本的加载。

  • afterLoad :在每个脚本加载后执行回调。

  • <


I如 Fluent界面方法然后我用这种方式构建模块:

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

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

I have a typescript application that dynamically adds script tags that point to JS files. 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:

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. 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)?

P.S. I would like to avoid using the onload callback to solve this issue since I noticed a performance degradation with my application. My second script file is very large and I am assuming that caused the degradation.

解决方案

There are some ways to overcome that requirement. For now I can suggest the followings:

  1. Use a library to inject dependencies (AMD or CommonJS modules)
  2. Create the script tag programmatically and set the attribute async = false.
  3. Create the script tag programmatically and set the callback onload in order to react when the script has been loaded asynchronously. The attribute async = true is set by default. (See example below)
  4. If you don't like previous options and you are allowed to modify the scripts to inject, then add a line at the end of scripts with a object/array that keep track of the scripts loaded.
  5. As a last resource, you can fetch the scripts as text, build a string with the scripts in the required order (wrap each text-script inside an IIFE) and finally execute the text-script through the horrible and dangerous eval().
  6. And the worst option, use a setInterval to check whether the script was executed. I added this last option only because I have seen some solutions using this bad technique.

If you are working in a big project, I would recommend the first option. But if you have a small project, you can opt for the third option. Why?

Let's consider that in the second option, by setting the attribute async = false will cause the browser blocking rendering until the script has been executed (bad practice).

I want to recommend a reading about script loaders: Deep dive into the murky waters of script loading, half hour worth spending!

I wrote an example of a small module to manage scripts injection, and this is the basic idea behind:

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

In this plunker you can test the injection of scripts asynchronously in a specific order:

The main idea was to create an API that allows you interact with the scripts to inject, by exposing the following methods:

  • addScript: receive the url or an Array of url's of the scripts.
  • load: Runs the task for load scripts in the specific order.
  • reset: Clear the array of scripts, or cancels the load of scripts.
  • afterLoad: Callback executed after every script has been loaded.
  • onComplete: Callback executed after all scripts have been loaded.

I like the Fluent Interface or method chaining way, then I built the module that way:

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

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 were loaded, the onComplete() callback is executed.

这篇关于加载动态添加的脚本标签的顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆