您当前位置: 南顺网络>> 官方资讯>> 建站知识

JS的阻塞加载和 defer 和 async 属性

JS具有阻塞特性,当浏览器在执行js代码时,不能同时做其它事情,即<script>每次出现都会让页面等待脚本的解析和执行(不论JS是内嵌的还是外链的),JS代码执行完成后,才继续渲染页面。

所有浏览器在下载JS的时候,会阻止一切其他活动,比如其他资源的下载,内容的呈现等等。至到JS下载、解析、执行完毕后才开始继续并行下载其他资源并呈现内容。

有人会问:为什么JS不能像CSS、image一样并行下载了?这里需要简单介绍一下浏览器构造页面的原理,
当浏览器从服务器接收到了HTML文档,并把HTML在内存中转换成DOM树,在转换的过程中如果发现某个节点(node)上引用了CSS或者IMAGE,就会再发1个request去请求CSS或image,然后继续执行下面的转换,而不需要等待request的返回,当request返回后,只需要把返回的内容放入到DOM树中对应的位置就OK。但当引用了JS的时候,浏览器发送1个js request就会一直等待该request的返回。因为浏览器需要1个稳定的DOM树结构,而JS中很有可能有代码直接改变了DOM树结构,比如使用document.write 或 appendChild,甚至是直接使用的location.href进行跳转,浏览器为了防止出现JS修改DOM树,需要重新构建DOM树的情况,所以就会阻塞其他的下载和呈现.

阻塞下载图:下图是访问blogjava首页的时间瀑布图,可以看出来开始的2个image都是并行下载的,而后面的2个JS都是阻塞下载的(1个1个下载)。

由于,JS的这种阻塞特性,每次遇到<script>,页面都必须停下来等待脚本下载并执行,这会停止页面绘制,带来不好的用户体验。所以,有必要减少JS阻塞特性造成的困扰。

1 优化脚本位置

HTML4规范中,<script>可以放在<head>或<body>中。你可能习惯性的在<head>中放置多个外链JS、CSS,以求优先加载它们。浏览器在继续到<body>之前,不会渲染页面,所以,把JS放在<head>中,会导致延迟。为了提高用户体验,新一代浏览器都支持并行下载JS,但是JS下载仍然会阻塞其它资源的下载(eg.图片)。尽管脚本的下载过程并不会相互影响,但页面仍然必须等待所有JS下载并执行完成才能继续。显见,所有<script>应该尽可能放到<body>的底部,以减少对页面下载的影响。

注意:CSS文件本身是并行下载,不会阻塞页面的其他进程。但是,如果把一段内嵌脚本放在引用外链CSS的<link>之后会导致页面阻塞去等待CSS的下载。这样做是为了确保内嵌脚本在执行时能够获得正确的样式信息。所以,最好不要把内嵌脚本放在CSS的<link>之后。


2 减少外链脚本数量以改善性能

 原因很简单,额外的HTTP请求会带来额外的开销,所以减少页面中外链脚本的数量,有助于改善性能。


3 使用无阻塞下载JS方法

无阻塞脚本的秘诀在于,在页面加载完成后才加载JS,即在window对象的load事件触发后在下载脚本。


4 使用<script>的defer属性(仅IE和Firefox3.5以上);

defer属性指明本元素所含的脚本不会修改DOM,因此代码能安全的延迟执行。defer属性的<script>,对应的JS文件将在页面解析到<script>时开始下载,但并不会执行,直到DOM加载完成,即onload事件触发前被调用。当一个带有defer属性的JS文件下载时,他不会阻塞浏览器的其它进程,因此这类文件可以与页面中的其他资源并行下载。


5 嵌入JS应该放在什么位置


   1、放在底部,虽然放在底部照样会阻塞所有呈现,但不会阻塞资源下载。
   
   2、如果嵌入JS放在head中,请把嵌入JS放在CSS头部。
   
   3、使用defer
   
   4、不要在嵌入的JS中调用运行时间较长的函数,如果一定要用,可以用setTimeout来调用
   

PS:很多网站喜欢在head中嵌入JS,并且习惯放在CSS后面,比如看到的www.qq.com,当然也有很多网站是把JS放到CSS前面的,比如yahoo,google


js的[defer]和[async]属性

HTML4.01为<script>定义了6个属性,包括defer和async。defer和async都是可选的,且只对外部脚本文件有效。



一、当浏览器解析到script脚本,没有defer或async时:

<script src="main.js"></script>


浏览器会立即加载并执行指定的脚本,“立即”指在渲染该script标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。


二、当浏览器解析到script脚本,有async时:


<script async src="main.js"></script>


浏览器会立即下载脚本,但不妨碍页面中的其他操作,比如下载其他资源或等待加载其他脚本。加载和渲染后续文档元素的过程和main.js的加载与执行并行进行(异步)。

async不保证按照脚本出现的先后顺序执行,因此,确保两者之前互不依赖非常重要,指定async属性的目的是不让页面等待两个脚本的下载和执行,从而异步加载页面其他内容,建议异步脚本不要在加载期间修改DOM。

异步脚本一定会在页面的load事件前执行,但可能会在DOMContentLoaded事件触发之前或之后执行。支持异步脚本的浏览器有Firefox 3.6、Safari 5 和Chrome


三、当浏览器解析到script脚本,有defer时:

<script defer="defer" src="main1.js"></script><script defer="defer"  src="main2.js"></script>

表示脚本会被延迟到文档完全被解析和显示之后再执行,加载后续文档元素的过程将和main.js的加载并行进行(异步)。

HTML5规范要求脚本按照它们出现的先后顺序执行,因此第一个延迟脚本会先于第二个延迟脚本执行,而这两个脚本会先于DOMContentLoaded事件。

在现实当中,延迟脚本并不一定会按照顺序执行,也不一定会在DOMContentLoaded事件触发前执行,因此最好只包含一个延迟脚本。

IE4~IE7还支持对嵌入脚本的defer属性,但IE8以及之后的版本则完全支持HTML5规定的行为。IE4,Firefox 3.5,Safari 5和Chrome是最早支持defer属性的浏览器。其他浏览器忽略这个属性,像平常一样处理脚本。为此,把延迟脚本放在页面底部仍然是最佳选择。


defer 和 async 在网络读取(下载)这块儿是一样的,都是异步的(相较于 HTML 解析)它俩的差别在于脚本下载完之后何时执行,显然 defer 是最接近我们对于应用脚本加载和执行的要求的


async 则是一个乱序执行的主,对它来说脚本的加载和执行是紧紧挨着的,所以不管你声明的顺序如何,只要它加载完了就会立刻执行,async 对于应用脚本的用处不大,因为它完全不考虑依赖(哪怕是最低级的顺序执行),不过它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说却是非常合适的,最典型的例子:Google Analytics。



原文链接:https://blog.csdn.net/ssisse/article/details/51698307

编辑:--ns868