JS脚本未按顺序下载和评估



我有3个<script>标记作为<body>中的最后一个元素。他们加载外部.js脚本:

...
</div>
<script src="https://somewhere.com/scripts/script1.js"></script>
<script src="https://somewhere.com/scripts/script2.js"></script>
<script src="https://somewhere.com/scripts/script3.js"></script>
</body>
</html>

所有脚本都只包含一行:console.log(Start script X),其中X是脚本编号。我期望脚本按顺序进行评估,输出为:

Start script 1
Start script 2
Start script 3

但输出似乎是随机的。更准确地说,评估的顺序似乎完全取决于脚本完成下载的顺序。

我期望评估顺序模仿HTML中引用的顺序是对的吗?如果没有,我如何确保它们按照正确的顺序进行评估(除了将它们全部合并到一个.js文件中之外(?

您的观察是正确的。我理解你的期望,但这样的评估顺序意味着文件是按顺序下载的,而不是按并行单元格下载的,或者它们是按顺序评估的,而非按并行单元格评估的。

让它们按顺序下载或执行会很麻烦。如果脚本从不下载怎么办?后续脚本是否应该永远保留?这将比目前的情况更糟。

如果脚本不相互依赖怎么办?他们是否应该相互等待,即使这样做毫无意义?当然不是。

所以实际的行为是正确的,但你是对的,这也意味着一些问题。

通常,这个问题的解决方案是将脚本分解为类和函数,这些类和函数可以在理想情况下执行,而不是在脚本下载时执行,并且在body标记中也有onload事件,或者通过AddEventListener创建loadevent

让我们假设在script1中有一个f1function,在script2中有f2function。。。

然后你可以做这样的事情:

window.addEventListener('load', (event) => {
f1();
f2();
//...
});

你甚至可以这样做:

function load() {
f1();
f2();
//...
}

然后将其添加到body:

<body onload="load()">
<!-- ... -->
</body>

选项1:defer

本文介绍了defer/asyc脚本标记的属性。标记为defer的脚本按照标记中定义的顺序执行(解析完成后(。

<script src="https://somewhere.com/scripts/script1.js" defer></script>
<script src="https://somewhere.com/scripts/script2.js" defer></script>
<script src="https://somewhere.com/scripts/script3.js" defer></script>
<script src="https://somewhere.com/scripts/script4.js" defer></script>

选项2:管理顺序加载

这个脚本应该按照顺序一个接一个地加载文件,并且应该保留您想要的序列

const scripts = [
"https://somewhere.com/scripts/script1.js",
"https://somewhere.com/scripts/script2.js",
"https://somewhere.com/scripts/script3.js",
"https://somewhere.com/scripts/script4.js"
];
let sctr = 0;
const doScript = () => {
if (sctr++ >= scripts.length) {
console.log('all scripts loaded syncronously');
return;
}
const scriptPromise = new Promise((resolve, reject) => {
const script = document.createElement('script');
document.body.appendChild(script);
script.onload = resolve;
script.onerror = reject;
script.async = true; // they're loading one at a time, so async is ok
script.src = scripts[sctr];
});
scriptPromise.then(() => {
console.log(scripts[sctr], 'loaded');
doScript();
},
(err) => {
console.error(scripts[sctr], 'failed', err);
// halting
});
}

最新更新