1、阻塞特性 |
《高性能JavaScript》一书中,关于第一章“Loading and Execution”,提到了无阻塞加载JavaScript技术,目的是为了提升页面呈现速度。javascript
说到无阻塞加载JavaScript要点,咱们就有必要知道,为何在html中不论是内联JavaScript仍是外联,会影响到页面的性能?css
缘由是:JavaScript是单线程,在JavaScript运行时其余的事情不能被浏览器处理。事实上,大多数浏览器使用单线程处理UI更新和JavaScript运行等多个任务,而同一时间只能有一个任务被执行。因此在执行JavaScript时,会妨碍其余页面动做。这是JavaScript的特性,咱们无法改变。html
而且,html解析过程是至上而下的,当html解析器遇到诸如<script>、<link>等标签时,解析器就会中止下来,去下载相应的内容。须要注意的是,在加载<script>、<link>标签时都会阻止解析器往下执行。html5
而且,html解析过程是至上而下的,当html解析器遇到诸如<script>、<link>等标签时,就会去下载相应内容。且加载、解析、执行JavaScript会阻止解析器往下执行。java
那何时,html解析器才能往下继续解析html文档呢?跨域
就JavaScript而言,当html解析器遇到<script>标签,不管它是内联仍是外联,页面中的下载和解析过程都必须中止,直到<script>从外部加载进来的JavaScript或内联的JavaScript运行完毕,方可继续解析。在高版本的浏览器当中,容许并行下载JavaScript文件,当一个<script>标签正在下载外部资源时,没必要阻塞其余<script>标签,可是不幸地是,JavaScript的下载仍然会阻塞其余资源的下载,例如图片。这里还须要值得注意的是,对于样式和脚本的前后顺序一样会影响到浏览器的解析过程,好比将<link>标签放在<script>标签前面,若是样式下载受阻,那么将阻塞<link>后面的<script>加载和执行,究其缘由主要在于:script脚本在执行过程当中可能会引用到相关样式。浏览器
了解了JavaScript在html中的阻塞特性,咱们再来看看如何改善其阻塞特性。安全
2、改善方法 |
--最简单作法--:app
为了让html文档在解析时,尽可能地快,常规的作法是将<script>标签放到</body>标签的前面,这样就不会阻塞html中其余资源的下载了。dom
以下:
尽管脚本下载之间互相阻塞,但页面已经下载完成而且显示在用户面前了,进入页面的速度不会显得太慢。且,为了让脚本之间的互相阻塞最小化,一般将多个相关的JavaScript文件合并为一个JavaScript文件,另外这样作带来的好处不只让脚本之间阻塞变小,还减小了http请求的数量。
但,这样作JavaScript文件下载之间仍是会阻塞,特别是当JavaScript文件逐渐变多时。
故而,引入无阻塞脚本技术。
无阻塞脚本技术主要分为两大类:
一、 HTML5中的defer和async;
2、 动态建立script为dom元素。
下面将分别介绍。
--HTML5中的defer和async--:
HTML5中提供了两个属性供<script>标签使用,目的就是为了无阻塞加载JavaScript。
用法以下:
<script src="file1.js" defer></script> <script src="file2.js" async></script>
须要注意的是,这两个属性对内联JavaScript是无效的,只针对外联JavaScript,如上所示。
加载流程:
当解析器遇到设置defer或者async属性的<script>元素时,它开始下载脚本,并继续解析文档。脚本会在它下载完成后尽快执行,可是解析器没有停下来等待他下载。
defer和async区别:
就defer和async的区别而言,使用defer的<script>标签是按照他们排列的顺序执行的,而使用async的<script>标签是不按他们在HTML中的排列顺序执行的;
就执行时间而言,defer是在DOMContentloaded事件以前执行,而async是在window.onload事件以前执行的,且只支持IE10+。当defer和async同时存在时,会忽略defer而遵循async。且使用defer和async的脚本禁止使用document.write方法哦。
--动态脚本元素--:
由于script标签是在html中的,是属于dom元素,因此咱们彻底能够利用dom方法建立一个动态的script元素。
以下:
var script = document.createElement('script'); script.type = 'text/javascript'; script.src = 'file1.js'; document.getElementsByTagName('head')[0].appendChild(script);
“当建立的script元素添加到页面后马上开始下载。此技术的重点在于:不管在何处启动下载,文件的下载和运行都不会阻塞其余页面的处理过程。你甚至能够将这些代码放在<head>部分而不会对其他部分的页面代码形成影响(除了用于下载文件的HTTP链接)”
上面加粗部分引至《高性能JavaScript》,当时在我读到这句话时,不是很理解,在前面“阻塞特性”一小节中,咱们提到JavaScript是单线程且与UI线程互排,那么JavaScript在运行时,怎么不会阻塞其余页面的处理过程呢?
为此,带着这一困惑在博客园问答中心提出了本身的观点并与道友讨论(‘博问点击此’)。
经过与道友讨论以及本身查看了相关文档后,有了本身看法:
之因此动态建立script元素去加载JavaScript文件,不会对页面其他操做影响,缘由以下:
一、html解析器将script当作了dom元素,而不是script标签,因此就不对其进行诸如加载、解析、运行时,中止页面中一切行为。打了个擦边球。
二、JavaScript是单线程,且与UI线程共享同一个线程,但这不表明浏览器就只有一个线程。因此在执行JavaScript代码时,不影响图片之类的下载。
好了,回到刚才采用动态脚本元素的方法,咱们还得完善下,缘由是上述代码,在‘自运行’时还好,可是若是引用了其余js文件中的方法呢?那就得出错咯。由于咱们没法保证动态脚本元素执行JavaScript代码的顺序。针对这一问题,标准浏览器咱们能够利用<script>节点的load事件处理,而IE浏览器咱们能够利用其特有的readystatechange事件处理。
封装好的代码以下:
function loadScript(url, callback){ var script = document.createElement('script'); script.type = 'text/javascript'; /* 在IE中readyState值所表示的最终状态并不一致, 有时<script>元素会获得"loaded"却不出现"complete", 但另一些状况下出现"complete"而用不到"loaded"。 最安全的办法就是在readystatechange事件中检查这两种状态, 而且当其中一种状态出现时,删除readystatechange事件句柄(保证事件不会被触发两次) */ if(script.readyState){//IE script.onreadystatechange = function(){ if(script.readyState == 'loaded' || script.readyState == 'complete'){ script.onreadystatechange = null; callback() } } }else{//Other script.onload = function(){ callback(); } } script.src = url; document.getElementsByTagName('head')[0].appendChild(script); }
因此,当页面中动态加载多个有关联的JavaScript文件时,咱们能够将其串联起来,保证顺序。
以下:
//串联起来 loadScript('file1.js',function(){ loadScript('file2.js',function(){ ... }); });
除开这种方法,还有一种就是“XHR脚本注入”,大致内容与上面的方法差很少,都须要动态建立script元素,区别在于该方法利用XMLHttpRequest对象,请求JavaScript文件,并将请求到的responseText,插入script元素的text中。由于是借助XMLHttpRequest对象,缺点显而易见,不能跨域请求。
示例代码以下:
var xhr = new XMLHttpRequest(); xhr.open('get', 'file1.js', true); xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ if(xhr.status >= 200 && xhr.status < 300 || xhr.status ==304){ var script = document.createElement('script'); script.type = 'text/javascript'; script.text = xhr.responseText; document.body.appendChild(script); } } }; xhr.send(null);
3、拓展阅读 |
[2] HTML渲染过程详解
[3] 浏览器加载渲染网页过程解析
[4] defer、async属性以及JS异步加载并执行解决方案