动态添加的脚本标签的加载顺序
Load ordering of dynamically added script tags
我有一个 typescript 应用程序,它动态添加指向 JS 文件的脚本标签。由于一些限制,我不能在 html 文件中静态定义这些脚本标签,所以我通过打字稿动态添加它们,如下所示:
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 回调来解决这个问题,因为我注意到我的应用程序性能下降。我的第二个脚本文件非常大,我假设它导致了性能下降。
我可以提及一些替代方案来克服该要求:
使用库注入依赖项(AMD 或 CommonJS 模块)
只需使用modules。 ES2015:import
/export
,或 CommonJS:require()
.
- 以编程方式创建
script
标记并设置回调 onload
以便在异步加载脚本时做出反应。默认设置 async = true
属性。
如果允许修改要注入的脚本,则在脚本末尾添加一行 object
或 array
以跟踪脚本已加载。
- 您可以将脚本作为文本 (
XMLHttpRequest
) 获取,然后按要求的顺序使用脚本构建 string
,最后通过 [=38= 执行文本脚本]
- 和不太推荐但经常使用的选项,设置一个
setInterval
来检查脚本是否已经执行。
我建议选择第一个选项。但出于学术目的,我将说明第二个选项:
Create the script
tag programmatically and set the callback onload
in order to react when the script has been loaded asynchronously.
我想推荐一本关于脚本加载器的读物:Deep dive into the murky waters of script loading,值得花半个小时!
下面的例子是一个管理脚本注入的小模块,这就是它背后的基本思想:
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"
执行相同的操作,之后已加载所有脚本,执行 onComplete()
回调。
I would like to avoid using the onload callback to solve this issue since I noticed a performance degradation with my application.
如果您想要订购文件...您需要等待每个文件的加载。没有办法解决它。
这是我曾经写过的一个效用函数:
/**
* 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
});
我有一个 typescript 应用程序,它动态添加指向 JS 文件的脚本标签。由于一些限制,我不能在 html 文件中静态定义这些脚本标签,所以我通过打字稿动态添加它们,如下所示:
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 回调来解决这个问题,因为我注意到我的应用程序性能下降。我的第二个脚本文件非常大,我假设它导致了性能下降。
我可以提及一些替代方案来克服该要求:
使用库注入依赖项(AMD 或 CommonJS 模块)
只需使用modules。 ES2015:import
/export
,或 CommonJS:require()
.- 以编程方式创建
script
标记并设置回调onload
以便在异步加载脚本时做出反应。默认设置async = true
属性。 如果允许修改要注入的脚本,则在脚本末尾添加一行object
或array
以跟踪脚本已加载。- 您可以将脚本作为文本 (
XMLHttpRequest
) 获取,然后按要求的顺序使用脚本构建string
,最后通过 [=38= 执行文本脚本] - 和不太推荐但经常使用的选项,设置一个
setInterval
来检查脚本是否已经执行。
我建议选择第一个选项。但出于学术目的,我将说明第二个选项:
Create the
script
tag programmatically and set the callbackonload
in order to react when the script has been loaded asynchronously.
我想推荐一本关于脚本加载器的读物:Deep dive into the murky waters of script loading,值得花半个小时!
下面的例子是一个管理脚本注入的小模块,这就是它背后的基本思想:
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"
执行相同的操作,之后已加载所有脚本,执行 onComplete()
回调。
I would like to avoid using the onload callback to solve this issue since I noticed a performance degradation with my application.
如果您想要订购文件...您需要等待每个文件的加载。没有办法解决它。
这是我曾经写过的一个效用函数:
/**
* 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
});