浏览器环境下JavaScript动态脚本与Ajax注入的执行时机分析

作者:小圆点时间:2026-10-29 20:35:48浏览:542

本文深入探讨浏览器环境中动态脚本与Ajax注入两种无阻塞加载技术的执行特性。通过多浏览器对比实验,分析动态脚本与Ajax注入在加载顺序、执行时机以及对DOMContentLoaded和window.onload事件的影响。结果显示,这两种技术均存在执行时机不确定性,且在不同浏览器中表现各异,可能阻塞页面加载事件,需结合回调机制确保执行顺序。

动态脚本与Ajax注入概述

在前文《浏览器环境下JavaScript脚本加载与执行探析之defer与async特性》中,我们详细分析了延迟加载(defer)和异步加载(async)脚本的执行时机、浏览器支持状况、已知的浏览器Bug及其他相关细节。除了这两种HTML5标准特性外,动态脚本创建和Ajax脚本注入也是实现无阻塞脚本加载的常用手段。尽管这两类方法都能确保脚本加载过程不阻塞页面的解析与渲染,但在不同浏览器中,它们所创建脚本的具体执行时机仍存在显著差异。本文将重点探讨这两种技术在不同环境下的执行特性。

代码准备:

为了进行实验,我们沿用《浏览器环境下JavaScript脚本加载与执行探析之代码执行顺序》2.3节中的loadScript函数来动态添加脚本,同时使用该文章2.4节中的loadXhrScript函数来实现通过Ajax注入脚本。我们将这两个工具函数统一放置在util.js文件中。

此外,本文实验所使用的浏览器版本如下:Chrome为47.0.2526.80,Firefox为43.0.4,Opera为30.0.1835.125。

动态脚本的执行时机分析

1 动态脚本

1.1动态脚本的执行时机问题

基于《浏览器环境下JavaScript脚本加载与执行探析之defer与async特性》2.3节的DEMO,我们额外引入三个外部JS文件:

dynamic1.js

test += "我是head外部动态脚本\n";

dynamic2.js

test += "我是body外部动态脚本\n";

dynamic3.js

test += "我是底部外部动态脚本\n";

1.1.1 DEMO1:动态脚本的执行时机初探

HTML代码结构如下:


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-"/>
<title>Dynamic Script Test</title>
<script src="http://lib.sinaapp.com/js/jquery/../jquery-...min.js"></script>
<script src="util.js"></script>
<script type="text/javascript">var test = "";</script>
<script>
loadScript("dynamic.js");
</script>
<script>
test += "我是head内部脚本\n";
</script>
<script src=".js" type="text/javascript"></script>
</head>
<body>
<button id="test">点击一下</button>
<script>
loadScript("dynamic.js");
</script>
<script src=".js" type="text/javascript"></script>
</body>
<script>
loadScript("dynamic.js");
</script>
<script src=".js" type="text/javascript"></script>
<script>
$(function(){
test += "我是DOMContentLoaded里面的脚本\n";
})
window.onload = function(){
test += "我是window.onload里面的脚本\n";
var button = document.getElementById("test");
button.onclick = function(){
console.log(test);
}
}
</script> 

在上述代码中,我们将三个动态脚本文件依次添加到HTML的<head>标签内,并通过与常规外部脚本及内部脚本的执行顺序进行对比,观察不同浏览器中的实际执行结果:

动态脚本与Ajax注入概述

注:firefox和opera中执行结果可能会变化

从上述示例可以看出,不同浏览器对动态脚本的执行时机处理存在较大差异,但以下两点结论是明确的:

[1]动态脚本确实能产生不阻塞后续脚本加载的效果,即具备延迟作用。然而,这种延迟作用并不一定持续至所有常规脚本执行完毕,也无法保证延迟到DOMContentLoaded事件触发之后。

[2]动态脚本的执行顺序无法得到保证。这一点在https://www.jb51.net/article/77920.htm及其后续几篇文章中已得到详细解释。

从该DEMO中还可以观察到,动态脚本的执行时机具有较大的不确定性。虽然在DEMO1中,动态脚本均在DOMContentLoaded事件之后执行,但这并不意味着动态脚本不会阻塞DOMContentLoaded。接下来我们通过DEMO2进一步验证:

1.1.2 DEMO2:动态脚本对DOMContentLoaded的阻塞

我们将DEMO1中的第27行代码修改为:


<script src="/delayfile.php?url=http://localhost/js/load/3.js&delay=5" type="text/javascript"></script> 

我们利用《浏览器环境下JavaScript脚本加载与执行探析之代码执行顺序》中的delayfile.php,将3.js的返回延迟5秒钟。我们知道,如果是defer延迟脚本,无论常规外部脚本延迟多久,defer脚本仍会在常规外部脚本之后执行;但动态脚本并非如此。我们查看修改后代码在浏览器中的执行顺序:

动态脚本与Ajax注入概述

注:firefox和opera中执行结果可能会变化

可以看到,当某个常规脚本加载时间较长时,动态脚本的执行明显提前。无论是在IE、Chrome、Firefox还是Opera中,动态脚本均在DOMContentLoaded事件触发之前执行。因此,我们可以初步判断,动态脚本的执行时机是不确定的,不同浏览器的处理机制存在差异。总体而言,动态脚本应在代码加载结束后,且线程中没有其他JavaScript代码执行时的某个时间点运行,但不同浏览器对这一时间点的把握各不相同。

因此,动态脚本是否会阻塞DOMContentLoaded也是不确定的,因为它可能在DOMContentLoaded触发之前或之后执行。此外,由于IE<=8不支持真正的DOMContentLoaded事件,jQuery在IE<=8中也是通过模拟判断该事件的发生(下一篇会专门讲解DOMContentLoaded事件),这在一定程度上也会影响上述代码的执行结果。

1.1.3 DEMO3:动态脚本与defer

我们知道,defer脚本具有相对明确的执行时机,即在页面解析完成后、DOMContentLoaded触发之前加载并执行。事实上,动态脚本与defer脚本在执行时机上并无直接关联。但在实际实验中发现,动态脚本可能在defer脚本之前或之后执行,但不会打断defer脚本的执行。我们再次引入《浏览器环境下JavaScript脚本加载与执行探析之defer与async特性》2.3节DEMO中的defer脚本,修改HTML代码如下:


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-"/>
<title>Dynamic Script Test</title>
<script src="http://lib.sinaapp.com/js/jquery/../jquery-...min.js"></script>
<script src="util.js"></script>
<script>
$(function(){
test += "我是DOMContentLoaded里面的脚本\n";
})
window.onload = function(){
test += "我是window.onload里面的脚本\n";
var button = document.getElementById("test");
button.onclick = function(){
console.log(test);
}
}
</script>
<script type="text/javascript">var test = "";</script>
<script>
loadScript("dynamic.js");
</script>
<script>
test += "我是head内部脚本\n";
</script>
<script src="defer.js" type="text/javascript" defer="defer"></script>
<script src=".js" type="text/javascript"></script>
</head>
<body>
<button id="test">点击一下</button>
<script>
loadScript("dynamic.js");
</script>
<script src="defer.js" type="text/javascript" defer="defer"></script>
<script src=".js" type="text/javascript"></script>
</body>
<script>
loadScript("dynamic.js");
</script>
<script src="defer.js" type="text/javascript" defer="defer"></script>
<script src=".js" type="text/javascript"></script>
</html> 

注:firefox和opera中执行结果可能会变化

我们增加了几个defer脚本,再来看一下各个浏览器中的执行结果:

动态脚本与Ajax注入概述

从实验结果可以看出,动态脚本的执行时机与defer脚本并没有直接关系。表面上看,在Chrome和Firefox中,延迟脚本总是在动态脚本之前执行。《前端优化-Javascript篇(2.异步加载脚本)》一文中提到“ScriptDOM和defer同时都可以执行,在不同浏览器中它们的优先级的不一样的。在Firfox和Chrome中,ScriptDOM的优先级比defer低,而在IE中情况则相反。”其实这种所谓的优先级并不存在,我们只需给defer脚本增加一个加载延迟,动态脚本的执行就会先于defer脚本。

1.2 动态脚本执行问题总结

我们再来总结一下动态脚本的执行问题:

[1]首先,动态脚本确实能在一定程度上起到延迟脚本执行的作用,但由于其执行时机的不确定性,这种延迟效果也是不可预知的。

[2]其次,动态脚本的执行顺序不一定按照添加的顺序,这是动态脚本技术的一大问题。最简单的解决方式是使用回调函数,监听脚本的加载状态,在一个脚本加载结束后再动态添加下一个脚本。

[3]动态脚本没有确切的执行时机。当通过DOM的appendChild、insertBefore等方法将script元素添加到DOM中时,就会去加载JS脚本。脚本的执行应在加载结束后的某个时机,不同浏览器对这个时机的处理差异较大。例如在IE中,应采取尽快执行的策略,即在加载结束后尽快寻找时机执行代码。

[4]动态脚本可能在DOMContentLoaded触发之前或之后执行,因此无法确定其是否会阻塞DOMContentLoaded。在一般情况下,动态脚本都会阻塞window.onload,但也存在动态脚本在window.onload触发之后执行,从而不阻塞window.onload的情况。

Ajax注入脚本的执行特性

2 Ajax注入脚本

2.1Ajax注入脚本的执行时机问题

Ajax脚本注入技术包含两种模式:同步加载和异步加载。同步加载的情况较为简单,脚本的加载和执行会阻塞后续代码的执行,直到注入的代码被加载和执行完毕。我们主要讨论异步模式下的情况:

2.1.1 DEMO4:Ajax注入脚本的执行问题初探

我们再添加3个外部文件:

ajax1.js

test += "我是head外部AJAX脚本\n";

ajax2.js

test += "我是body外部AJAX脚本\n";

ajax3.js

test += "我是底部外部AJAX脚本\n";

HTML的代码为:


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-"/>
<title>Ajax Script Test</title>
<script src="http://lib.sinaapp.com/js/jquery/../jquery-...min.js"></script>
<script src="util.js"></script>
<script type="text/javascript">var test = "";</script>
<script>
$(function(){
test += "我是DOMContentLoaded里面的脚本\n";
})
window.onload = function(){
test += "我是window.onload里面的脚本\n";
var button = document.getElementById("test");
button.onclick = function(){
console.log(test);
}
}
</script>
<script>
loadXhrScript("ajax.js",true);
</script>
<script>
test += "我是head内部脚本\n";
</script>
<script src=".js" type="text/javascript"></script>
</head>
<body>
<button id="test">点击一下</button>
<script>
loadXhrScript("ajax.js",true);
</script>
<script src=".js" type="text/javascript"></script>
</body>
<script>
loadXhrScript("ajax.js",true);
</script>
<script src=".js" type="text/javascript"></script>
</html> 

在这段代码中,我们分别在<head>标签内部、<body>标签内部、<body>标签外部共添加了3个注入脚本,通过正常引入的脚本作为参照,我们看一下在浏览器中的执行结果:

动态脚本与Ajax注入概述

注:firefox、opera、IE中的执行结果可能会变化

从上述执行结果中,我们可以看出,Ajax注入脚本的执行时机具有更大的不确定性。事实上,与动态脚本类似,Ajax注入脚本的加载过程也是异步的,因此,完成加载的时间首先是不确定的。其次,浏览器在脚本加载完成后何时执行加载的代码同样也是不确定的。对于异步模式下的Ajax注入脚本的执行时机,我们总结如下:

[1]Ajax注入的脚本也具有一定的延迟作用,但是与动态脚本类似,延迟的时间是不确定的。

[2]Ajax注入脚本的执行顺序是无序的,虽然DEMO4中的例子看起来注入的脚本都是按照添加的顺序执行的,但是只要稍微理解异步以及动态脚本执行顺序问题,就应该能够明白这一点。

[3]Ajax注入脚本的执行时机也是不确定的,与脚本的加载时间以及浏览器的处理机制有关。

[4]由于上述的几点,Ajax注入的脚本可能会阻塞DOMContentLoaded,也可能会阻塞window.onload。

作者最新文章