浏览器环境下JavaScript脚本加载与执行探析之动态脚本与Ajax脚本注入

在《浏览器环境下JavaScript脚本加载与执行探析之defer与async特性》中,我们研究了延迟脚本(defer)和异步脚本(async)的执行时机、浏览器支持情况、浏览器bug以及其他的细节问题。而除了defer和async特性,动态脚本Ajax脚本注入也是两种常用的创建无阻塞脚本的方法。总的来看,这两种方法都能达到脚本加载不影响页面解析和渲染的作用,但是在不同的浏览器中,这两种技术所创建的脚本的执行时机还是有一定差异,今天我们再来探讨一下通过动态脚本技术和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

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

dynamic2.js

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

dynamic3.js

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

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

HTML的代码为:

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta charset="UTF-8"/>
 5     <title>Dynamic Script Test</title>
 6     <script src="http://lib.sinaapp.com/js/jquery/1.9.1/jquery-1.9.1.min.js"></script>
 7     <script src="util.js"></script>
 8     <script type="text/javascript">var test = "";</script>
 9     <script>
10         loadScript("dynamic1.js");
11     </script>
12     <script>
13         test += "我是head内部脚本\n";
14     </script>
15     <script src="1.js" type="text/javascript"></script>
16 </head>
17 <body>
18 <button id="test">点击一下</button>
19 <script>
20     loadScript("dynamic2.js");
21 </script>
22 <script src="2.js" type="text/javascript"></script>
23 </body>
24 <script>
25     loadScript("dynamic3.js");
26 </script>
27 <script src="3.js" type="text/javascript"></script>
28 <script>
29     $(function(){
30         test += "我是DOMContentLoaded里面的脚本\n";
31     })
32     window.onload = function(){
33         test += "我是window.onload里面的脚本\n";
34         var button = document.getElementById("test");
35         button.onclick = function(){
36             console.log(test);
37         }
38     }
39 </script>

在代码中,我们先后将3个动态脚本文件加入到HTML的<head>标签中,同时通过与正常外部脚本和内部脚本的执行来进行比较,我们看一下不同浏览器中的执行结果:

IE7 IE9 IE10 CHROME firefox opera

我是head内部脚本
我是head外部动态脚本
我是head外部脚本
我是body外部动态脚本
我是body外部脚本
我是底部外部动态脚本
我是底部外部脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部动态脚本
我是head外部脚本
我是body外部动态脚本
我是body外部脚本
我是底部外部动态脚本
我是底部外部脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部动态脚本
我是head外部脚本
我是body外部动态脚本
我是body外部脚本
我是底部外部动态脚本
我是底部外部脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部脚本
我是body外部脚本
我是底部外部脚本
我是DOMContentLoaded里面的脚本
我是head外部动态脚本
我是body外部动态脚本
我是底部外部动态脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部脚本
我是body外部脚本
我是底部外部脚本
我是DOMContentLoaded里面的脚本
我是底部外部动态脚本
我是body外部动态脚本
我是head外部动态脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部脚本
我是head外部动态脚本
我是body外部动态脚本
我是body外部脚本
我是底部外部脚本
我是DOMContentLoaded里面的脚本
我是底部外部动态脚本
我是window.onload里面的脚本

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

从上面的例子中,我们可以看出,在不同浏览器中,动态脚本的执行时机差异还是比较大的,但以下两点是可以明确的:

[1]动态脚本确实可以起到不阻塞后续脚本的作用,即延迟作用,但是这个延迟作用却不一定能够持续到所有的正常脚本都执行完毕之后,也无法保证能够延迟到DOMContentLoaded之后

[2]动态脚本执行的先后顺序是无法保证的,这一点在http://www.cnblogs.com/sanshi/archive/2011/02/28/1967367.html这篇文章以及后续的几篇文章中进行了详细的解释

从这个DEMO中还可以看出,动态脚本的执行时机具有较大的不确定性,虽然在DEMO1中,动态脚本都在DOMContentLoaded事件之后执行,但却也并不意味着动态脚本就不会阻塞DOMContentLoaded,我们通过DEOM2来看一下:

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

我们把DEMO1中的第27行内码修改为:

1 <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脚本还是会在正常外部脚本之后执行的,但是动态脚本却不是这样了,我们看一下修改后的代码在浏览器中的执行顺序:

IE7 IE9 IE10 CHROME firefox opera

我是head内部脚本
我是head外部动态脚本
我是head外部脚本
我是body外部动态脚本
我是body外部脚本
我是底部外部动态脚本
我是底部外部脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部脚本
我是body外部脚本
我是head外部动态脚本
我是body外部动态脚本
我是底部外部动态脚本
我是底部外部脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部脚本
我是body外部脚本
我是head外部动态脚本
我是body外部动态脚本
我是底部外部动态脚本
我是底部外部脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部脚本
我是body外部脚本
我是body外部动态脚本
我是head外部动态脚本
我是底部外部动态脚本
我是底部外部脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部脚本
我是body外部脚本
我是底部外部动态脚本
我是body外部动态脚本
我是head外部动态脚本
我是底部外部脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部脚本
我是head外部动态脚本
我是body外部脚本
我是body外部动态脚本
我是底部外部动态脚本
我是底部外部脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本

注: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脚本的执行,我们再引入《浏览器环境下JavaScript脚本加载与执行探析之defer与async特性》中2.3节的DEMO中的defer脚本,修改HTML代码如下:

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta charset="UTF-8"/>
 5     <title>Dynamic Script Test</title>
 6     <script src="http://lib.sinaapp.com/js/jquery/1.9.1/jquery-1.9.1.min.js"></script>
 7     <script src="util.js"></script>
 8     <script>
 9         $(function(){
10             test += "我是DOMContentLoaded里面的脚本\n";
11         })
12         window.onload = function(){
13             test += "我是window.onload里面的脚本\n";
14             var button = document.getElementById("test");
15             button.onclick = function(){
16                 console.log(test);
17             }
18         }
19     </script>
20     <script type="text/javascript">var test = "";</script>
21     <script>
22         loadScript("dynamic1.js");
23     </script>
24     <script>
25         test += "我是head内部脚本\n";
26     </script>
27     <script src="defer1.js" type="text/javascript" defer="defer"></script>
28     <script src="1.js" type="text/javascript"></script>
29 </head>
30 <body>
31 <button id="test">点击一下</button>
32 <script>
33     loadScript("dynamic2.js");
34 </script>
35 <script src="defer2.js" type="text/javascript" defer="defer"></script>
36 <script src="2.js" type="text/javascript"></script>
37 </body>
38 <script>
39     loadScript("dynamic3.js");
40 </script>
41 <script src="defer3.js" type="text/javascript" defer="defer"></script>
42 <script src="3.js" type="text/javascript"></script>
43 </html>

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

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

IE7 IE9 IE10 CHROME firefox opera

我是head内部脚本
我是head外部动态脚本
我是head外部脚本
我是body外部动态脚本
我是body外部脚本
我是底部外部动态脚本
我是底部外部脚本
我是head外部延迟脚本
我是body外部延迟脚本
我是底部外部延迟脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部动态脚本
我是head外部脚本
我是body外部动态脚本
我是body外部脚本
我是底部外部动态脚本
我是底部外部脚本
我是head外部延迟脚本
我是body外部延迟脚本
我是底部外部延迟脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部动态脚本
我是head外部脚本
我是body外部脚本
我是body外部动态脚本
我是底部外部动态脚本
我是底部外部脚本
我是head外部延迟脚本
我是body外部延迟脚本
我是底部外部延迟脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部脚本
我是body外部脚本
我是底部外部脚本
我是head外部延迟脚本
我是body外部延迟脚本
我是底部外部延迟脚本
我是DOMContentLoaded里面的脚本
我是head外部动态脚本
我是body外部动态脚本
我是底部外部动态脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部脚本
我是body外部脚本
我是底部外部脚本
我是head外部延迟脚本
我是body外部延迟脚本
我是底部外部延迟脚本
我是head外部动态脚本
我是DOMContentLoaded里面的脚本
我是body外部动态脚本
我是底部外部动态脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部动态脚本
我是head外部脚本
我是body外部脚本
我是body外部动态脚本
我是底部外部动态脚本
我是底部外部脚本
我是head外部延迟脚本
我是body外部延迟脚本
我是底部外部延迟脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本

从实验结果可以看出,动态脚本的执行时机与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

2 Ajax注入脚本

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

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

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

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

ajax1.js

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

ajax2.js

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

ajax3.js

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

HTML的代码为:

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta charset="UTF-8"/>
 5     <title>Ajax Script Test</title>
 6     <script src="http://lib.sinaapp.com/js/jquery/1.9.1/jquery-1.9.1.min.js"></script>
 7     <script src="util.js"></script>
 8     <script type="text/javascript">var test = "";</script>
 9     <script>
10         $(function(){
11             test += "我是DOMContentLoaded里面的脚本\n";
12         })
13         window.onload = function(){
14             test += "我是window.onload里面的脚本\n";
15             var button = document.getElementById("test");
16             button.onclick = function(){
17                 console.log(test);
18             }
19         }
20     </script>
21     <script>
22         loadXhrScript("ajax1.js",true);
23     </script>
24     <script>
25         test += "我是head内部脚本\n";
26     </script>
27     <script src="1.js" type="text/javascript"></script>
28 </head>
29 <body>
30 <button id="test">点击一下</button>
31 <script>
32     loadXhrScript("ajax2.js",true);
33 </script>
34 <script src="2.js" type="text/javascript"></script>
35 </body>
36 <script>
37     loadXhrScript("ajax3.js",true);
38 </script>
39 <script src="3.js" type="text/javascript"></script>
40 </html>

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

IE7 IE9 IE10 CHROME firefox opera

我是head内部脚本
我是head外部AJAX脚本
我是head外部脚本
我是body外部AJAX脚本
我是body外部脚本
我是底部外部AJAX脚本
我是底部外部脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部AJAX脚本
我是head外部脚本
我是body外部AJAX脚本
我是body外部脚本
我是底部外部脚本
我是底部外部AJAX脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本


我是head内部脚本
我是head外部AJAX脚本
我是head外部脚本
我是body外部AJAX脚本
我是body外部脚本
我是底部外部脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本
我是底部外部AJAX脚本


我是head内部脚本
我是head外部脚本
我是body外部脚本
我是底部外部脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本
我是head外部AJAX脚本
我是body外部AJAX脚本
我是底部外部AJAX脚本


我是head内部脚本
我是head外部脚本
我是body外部脚本
我是底部外部脚本
我是head外部AJAX脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本
我是body外部AJAX脚本
我是底部外部AJAX脚本


我是head内部脚本
我是head外部脚本
我是head外部AJAX脚本
我是body外部脚本
我是body外部AJAX脚本
我是底部外部脚本
我是DOMContentLoaded里面的脚本
我是window.onload里面的脚本
我是底部外部AJAX脚本

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

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

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

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

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

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

由于个人经验尚浅,同时文章中所做的实验的浏览器覆盖率不够大,欢迎大家指正问题与讨论~~

时间: 2024-10-11 17:36:32

浏览器环境下JavaScript脚本加载与执行探析之动态脚本与Ajax脚本注入的相关文章

浏览器环境下JavaScript脚本加载与执行探析之代码执行顺序

本文主要基于向HTML页面引入JavaScript的几种方式,分析HTML中JavaScript脚本的执行顺序问题 1. 关于JavaScript脚本执行的阻塞性 JavaScript在浏览器中被解析和执行时具有阻塞的特性,也就是说,当JavaScript代码执行时,页面的解析.渲染以及其他资源的下载都要停下来等待脚本执行完毕①.这一点是没有争议的,并且在所有浏览器中的行为都是一致的,原因也不难理解:浏览器需要一个稳定的DOM结构,而JavaScript可能会修改DOM(改变DOM结构或修改某个

浏览器中Javascript的加载和执行

在刚学习Javascript时曾对该问题在小组内做个一次StudyReport,发现其中的基础还是值得分析的. 从标题分析,可以加个Javascript的加载和执行分为两个阶段:加载.执行.而加载即浏览器下载JS脚本的过程,执行时浏览器JS引擎解释执行的过程. 接下来先分析JS脚本加载的过程,加载方式可分为同步加载和异步加载. 同步加载即浏览器加载JS过程中停止对HTML元素的解析,保证JS执行的安全一致性,但如果JS中包含大量计算时,会导致阻塞页面的渲染.常见的JS加载是通过<script>

javascript页面加载完执行事件

<script type="text/javascript" language="JavaScript"> //: 判断网页是否加载完成 document.onreadystatechange = function () { if(document.readyState=="complete") { alert('ok'); } } </script> javascript页面加载完执行事件

高性能JavaScript(加载和执行)

当浏览器遇到 <script> 标签时,它是没办法知道 JavaScript 是否会向DOM中添加内容或引入其他元素,甚至关闭某一个标签.因此这个时候浏览器就会停止处理页面,先执行JavaScript代码,然后再继续解析和渲染页面. 改善 将<script>标签放到 <body>的底部,尽量减少对整个页面下载的影响. 减少<script>标签的数量,不仅仅是外链脚本,内嵌的脚本数量同样也要限制.(合并) 无阻塞脚本 无阻塞脚本的秘诀在于,在页面加载完成后才加

如何加快JavaScript的加载与执行

JS 有个很无语的阻塞特性,就是当浏览器在执行JS 代码时,不能同时做其他任何事情,无论其代码是内嵌的还是外部的. 浏览器在碰到一个引入外部JS 文件的<script>标签时会停下所有工作来下载并解析执行它,在这个过程中,页面渲染和用户交互完全被阻塞了,为了避免页面加载时的停顿甚至空白页的出现,JS 脚本应尽量放置在页面底部,这点很重要:兰西县璩家摄影 <html> <head> <title>无标题文档</title> <link rel

JavaScript的加载和执行

总结: 在</body>闭合之前,将所有的<script>标签放到页面底部.这样能确保在脚本执行之前页面已经完成渲染. 合并脚本, 页面中的<script>标签越少, 加载速度越快,响应也迅速. 无阻塞下载javaScript的方法,见下面 1. 脚本位置,组织脚本& 由于脚本会阻塞页面其他资源的下载,推荐所有的<script>标签尽可能放在<body>标签的底部,以尽量减小对整个页面下载的影响 例如:<body><sc

JavaScript学习--Item26 异步的脚本加载

先来看这行代码: <script src = "allMyClientSideCode.js"></script> 这有点儿--不怎么样."这该放在哪儿?"开发人员会奇怪,"靠上点,放到<head>标签里?还是靠下点,放到<body>标签里?"这两种做法都会让富脚本站点的下场很凄惨.<head>标签里的大脚本会滞压所有页面渲染工作,使得用户在脚本加载完毕之前一直处于"白屏死机&

不得不说的JavaScript异步加载

同步加载的问题 默认的js是同步加载的,这里的“加载”可以理解成是解析.执行,而不是“下载”,在最新版本的浏览器中,浏览器对于代码请求的资源都是瀑布式的加载,而不是阻塞式的,但是js的执行总是阻塞的.这会引起什么问题呢?如果我的index页面要加载一些js,但是其中的某个请求迟迟得不到响应,于是阻塞了后面的js代码的执行(同步加载),同时页面渲染也不能继续(如果js引入是在head标签后). <script type="text/javascript" src='http://c

你不知道的JavaScript--Item26 异步的脚本加载

先来看这行代码: <script src = "allMyClientSideCode.js"></script> 这有点儿--不怎么样."这该放在哪儿?"开发人员会奇怪,"靠上点,放到<head>标签里?还是靠下点,放到<body>标签里?"这两种做法都会让富脚本站点的下场很凄惨.<head>标签里的大脚本会滞压所有页面渲染工作,使得用户在脚本加载完毕之前一直处于"白屏死机&