悠悠楠杉
网站页面
正文:
在现代Web开发中,异步加载脚本是提升页面性能的关键手段之一。然而,异步操作带来的不确定性可能引发竞态条件(Race Condition),导致脚本依赖关系错乱或执行顺序异常。本文将系统分析问题根源,并提供可落地的优化方案。
<script>标签:
const script = document.createElement('script');
script.src = 'https://example.com/library.js';
document.head.appendChild(script);
这种方式虽然简单,但无法直接监听加载完成事件,容易与其他脚本产生依赖冲突。
defer与async属性
HTML5提供的原生异步支持:
async:下载完成后立即执行,不保证顺序。defer:延迟到DOM解析完成后按顺序执行。适用于无依赖关系的独立脚本,但对复杂场景控制力不足。
当多个异步脚本存在依赖关系时(例如A依赖B),若B未加载完成A已执行,会导致以下问题:
- 调用未定义的函数或变量
- 初始化顺序错误引发逻辑异常
案例模拟:
// 假设utils.js依赖lodash
loadScript('lodash.js'); // 异步加载
loadScript('utils.js'); // 可能先于lodash完成
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
// 按顺序加载
loadScript('lodash.js')
.then(() => loadScript('utils.js'))
.then(() => initApp());
Promise.all优化多脚本并行加载,再触发后续逻辑:
Promise.all([
loadScript('lib1.js'),
loadScript('lib2.js')
]).then(() => {
// 所有脚本就绪后执行
});
import()动态导入:
import('module.js')
.then(module => {
module.init();
});
function loadWithTimeout(src, timeout = 5000) {
return Promise.race([
loadScript(src),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeout)
)
]);
}
async function loadWithRetry(src, retries = 3) {
try {
await loadScript(src);
} catch (err) {
if (retries > 0) {
await new Promise(r => setTimeout(r, 1000 * (4 - retries)));
return loadWithRetry(src, retries - 1);
}
throw err;
}
}
通过合理选择异步控制策略,开发者能有效规避竞态风险。建议在复杂项目中结合Webpack/Rollup等构建工具,从根源上管理依赖关系。