这里有新鲜出炉的 Javascript 教程,程序狗速度看过来!
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
这篇文章主要介绍了浏览器环境下 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
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>
在代码中,我们先后将 3 个动态脚本文件加入到 HTML 的标签中,同时通过与正常外部脚本和内部脚本的执行来进行比较,我们看一下不同浏览器中的执行结果:
注:firefox 和 opera 中执行结果可能会变化
从上面的例子中,我们可以看出,在不同浏览器中,动态脚本的执行时机差异还是比较大的,但以下两点是可以明确的:
[1] 动态脚本确实可以起到不阻塞后续脚本的作用,即延迟作用,但是这个延迟作用却不一定能够持续到所有的正常脚本都执行完毕之后,也无法保证能够延迟到 DOMContentLoaded 之后
[2] 动态脚本执行的先后顺序是无法保证的,这一点在 / article/16/0821/267611.html 这篇文章以及后续的几篇文章中进行了详细的解释
从这个 DEMO 中还可以看出,动态脚本的执行时机具有较大的不确定性,虽然在 DEMO1 中,动态脚本都在 DOMContentLoaded 事件之后执行,但却也并不意味着动态脚本就不会阻塞 DOMContentLoaded,我们通过 DEOM2 来看一下:
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 脚本还是会在正常外部脚本之后执行的,但是动态脚本却不是这样了,我们看一下修改后的代码在浏览器中的执行顺序:
注: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 代码如下:
- <!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 的脚本,再来看一下各个浏览器中的执行结果:
从实验结果可以看出,动态脚本的执行时机与 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
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>
在这段代码中,我们分别在标签内部、标签内部、标签外部共添加了 3 个注入脚本,通过正常引入的脚本作为参照,我们看一下在浏览器中的执行结果:
注:firefox、opera、IE 中的执行结果可能会变化
从这个执行结果中,我们就可以看到,Ajax 注入脚本的执行时机具有更大的不确定性,事实上,与动态脚本类似,Ajax 注入脚本的加载过程也是异步的,因此,完成加载的时间首先是不确定的,其次,浏览器在脚本加载完成后何时执行加载的代码同样也是不确定的,对于异步模式下的 Ajax 注入脚本的执行时机,我们总结如下:
[1]Ajax 注入的脚本也具有一定的延迟作用,但是与动态脚本类似,延迟的时间是不确定的。
[2]Ajax 注入脚本的执行顺序是无序的,虽然 DEMO4 中的例子看起来注入的脚本都是按照添加的顺序执行的,但是只要稍微理解异步以及动态脚本执行顺序问题,就应该能够明白这一点。
[3]Ajax 注入脚本的执行时机也是不确定的,与脚本的加载时间以及浏览器的处理机制有关。
[4] 由于上述的几点,Ajax 注入的脚本可能会阻塞 DOMContentLoaded,也可能会阻塞 window.onload。
来源: http://www.phperz.com/article/17/0408/267510.html