动态添加Script解析顺序异常:如何确保script2优先于script3执行?
我懂你现在遇到的麻烦了——你本来希望三个脚本按script1.js→script2.js→script3.js的顺序执行,控制台输出1 2 3,结果实际跑出来是1 3 2,哪怕给script3.js加了defer也没搞定。咱们先拆解下问题根源,再给你几个靠谱的解决办法。
你的现有代码
HTML文件
<html> <head> <script src="script1.js"></script> <script defer type='text/javascript' src="script3.js"></script> </head> </html>
script1.js
console.log('1'); var head = document.getElementsByTagName('head')[0]; const url = 'script2.js'; var script = document.createElement('script'); script.type = 'text/javascript'; script.src = url; head.appendChild(script);
script2.js
console.log('2');
script3.js
console.log('3');
为什么会出现1 3 2的输出?
核心问题在于动态脚本和defer脚本的执行时机规则差异:
script1.js是同步脚本,浏览器会立即执行它,所以先输出1。- 执行
script1.js时动态创建的script2.js,默认是异步加载执行的——浏览器会在后台下载它,但不会阻塞后续的文档解析或其他脚本的执行。 script3.js加了defer属性,它的规则是:浏览器后台下载,但会等到整个HTML文档解析完成后,再按顺序执行所有带defer的脚本。
因为你的HTML结构非常简单,文档解析会很快完成,这时候defer的script3.js就会立即执行(输出3),而如果script2.js的下载还没完成,它就会在script3.js执行完毕后才下载完成并执行(输出2),最终就出现了1 3 2的顺序。
解决方案
这里有几个可靠的方法来保证你想要的执行顺序:
方法一:给动态添加的script2.js设置async=false
动态创建的脚本默认async为true,把它改成false后,脚本会变成同步加载执行——浏览器会等待script2.js下载并执行完成后,才继续解析文档和执行后续的defer脚本。修改script1.js如下:
console.log('1'); var head = document.getElementsByTagName('head')[0]; const url = 'script2.js'; var script = document.createElement('script'); script.type = 'text/javascript'; script.src = url; script.async = false; // 关键:禁用异步,强制同步执行 head.appendChild(script);
这样执行顺序就会是:script1.js执行→script2.js下载并执行→文档解析完成→script3.js执行,控制台输出1 2 3。
方法二:在script2.js加载完成后再加载script3.js
放弃在HTML中直接引入script3.js,而是在script1.js里监听script2.js的加载完成事件,再动态添加script3.js,这样能严格保证顺序:
console.log('1'); var head = document.getElementsByTagName('head')[0]; // 先加载script2.js const script2 = document.createElement('script'); script2.type = 'text/javascript'; script2.src = 'script2.js'; // 监听script2加载完成事件 script2.onload = function() { // 加载完成后再添加script3.js const script3 = document.createElement('script'); script3.type = 'text/javascript'; script3.src = 'script3.js'; head.appendChild(script3); }; head.appendChild(script2);
同时修改HTML,去掉原来的script3.js引入:
<html> <head> <script src="script1.js"></script> </head> </html>
这种方法的优势是完全由你控制脚本的加载顺序,不会受浏览器解析规则的影响。
方法三:调整script3.js的引入位置(最简单的方式)
如果你的场景允许,直接把script3.js放在HTML的body底部,并且去掉defer属性,利用同步脚本的执行顺序规则:
<html> <head> <script src="script1.js"></script> </head> <body> <!-- 页面内容 --> <script type='text/javascript' src="script3.js"></script> </body> </html>
不过这个方法的可靠性依赖于script2.js的加载速度,如果script2.js是大文件,可能还是会出现script3.js先执行的情况,所以更推荐前两种方法。
内容的提问来源于stack exchange,提问作者opowell

