「ブラウザ環境での JavaScript スクリプトの読み込みと実行の分析: 遅延機能と非同期機能」では、遅延スクリプト (遅延) と非同期スクリプト (async) 条件の実行タイミングとブラウザーのサポートを調査しました。ブラウザのバグやその他の詳細。遅延機能と非同期機能に加えて、動的スクリプトと Ajax スクリプト インジェクションも、ノンブロッキング スクリプトを作成するためによく使用される 2 つの方法です。一般に、これら 2 つの方法は、ページの解析やレンダリングに影響を与えることなくスクリプトの読み込みの効果を実現できますが、ブラウザによっては、これら 2 つのテクノロジによって作成されたスクリプトの実行タイミングに依然として一定の違いがあります。これらのスクリプトの機能は、ダイナミック スクリプト テクノロジと 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 スクリプトの読み込みと実行の分析: 遅延機能と非同期機能」のセクション 2.3 のデモに基づいて、3 つの外部 js ファイルを追加しました。
dynamic1.js
test += "私は外部動的スクリプトの先頭です ";
dynamic2.js
test += "私は本体の外部にある動的スクリプトです ";
動的3.js
test += "私は最下位の外部動的スクリプトです ";
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里面的脚本 "; }) window.onload = function(){ test += "我是window.onload里面的脚本 "; var button = document.getElementById("test"); button.onclick = function(){ console.log(test); } } </script>
このコードでは、HTML の
タグに 3 つの動的スクリプト ファイルを追加し、通常の外部スクリプトと内部スクリプトの実行を比較してみます。 :注: Firefox と Opera では実行結果が変わる可能性があります
上記の例から、異なるブラウザーでの動的スクリプトの実行タイミングは依然として大きく異なることがわかりますが、次の 2 つの点は明らかです。
[1] 動的スクリプトは確かに後続のスクリプトをブロックしない役割、つまり遅延効果を果たしますが、この遅延効果は通常のスクリプトがすべて実行されるまで持続しない可能性があり、持続できるという保証はありません。 DOMContentLoaded
まで遅延します。
[2] 動的スクリプトの実行順序は保証できません。これについては、http://www.jb51.net/article/77920.htmこの記事とその後のいくつかの記事で説明されています。
で
このデモからも、動的スクリプトの実行タイミングが非常に不確実であることがわかります。DEMO1 では、動的スクリプトは DOMContentLoaded イベントの後に実行されますが、動的スクリプトが実行されないわけではありません。 DEOM2 を見てみましょう:
1.1.2 デモ 2: 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里面的脚本 "; }) window.onload = function(){ test += "我是window.onload里面的脚本 "; 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里面的脚本 "; }) window.onload = function(){ test += "我是window.onload里面的脚本 "; 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。