在访问网页的时候,浏览器是如何渲染当前的页面的?浏览器获取到输入(HTML、CSS、Javascript)的时候,输入中有DOM Tree的信息,有样式信息,Javascript代码,在加载页面的时候,如果DOM的加载和解析太耗时的话,首屏的大量留白是很影响用户体验的,这里我们来看看,对于Scritp在页面中的加载对HTML的加载的性能影响。
script标签在页面中放置的位置对于整个页面的加载和功能影响是很大,默认情况下,浏览器在解析HTMl页面的时候,遇到script标签,如果是inline的话,会立即执行script标签中的代码,这里就牵扯一个问题,浏览器对HTML的解析是按照HTML内容的输入顺序执行的,如果script在head中,如果需要操作DOM,这个时候浏览器对文档的解析并没有结束,可能这时候需要的DOM并不存在。
<html>
<head>
<link rel="stylesheet" href="./style.css"></link>
<script type="text/javascript">
function foo() {
console.log('Dom Length: ', document.getElementsByClassName('test').length);
}
foo();
</script>
</head>
<body>
<div class="test">Test1</div>
<div class="test">Test2</div>
<div class="test">Test3</div>
<div class="test">Test4</div>
<div class="test">Test5</div>
</body>
</html>如上,是一个inline的script,在script的代码中去取DOM,这个时候浏览器在执行这段代码的时候,浏览器并没有解析到对应的DOM。
所以,为了避免这种情况,一般来说,会将script标签放置在</body>标签前,这个时候,所有的DOM都解析好了,不存在找不到DOM的情况。
<html>
<head>
<link rel="stylesheet" href="./style.css"></link>
</head>
<body>
<div class="test">Test1</div>
<div class="test">Test2</div>
<div class="test">Test3</div>
<div class="test">Test4</div>
<div class="test">Test5</div>
<script type="text/javascript">
function foo() {
console.log('Dom Length: ', document.getElementsByClassName('test').length);
}
foo();
</script>
</body>
</html>
在加载外部的script的时候,script的加载对于页面的性能影响更加明显,加载外部脚本的时候牵扯发外部的请求去请求脚本,这个时候HTML的解析是被阻塞住的,外部script加载成功后,马上执行script脚本,执行完后,才继续解析剩下的HTML。
<html>
<head>
<link rel="stylesheet" href="./style.css"></link>
</head>
<body>
<div class="test">Test1</div>
<div class="test">Test2</div>
<div class="test">Test3</div>
<div class="test">Test4</div>
<div class="test">Test5</div>
<script type="text/javascript" src="./app.js"></script>
</body>
</html>
从上图的profile的图我们可以发现,浏览器在解析HTML的时候,默认在解析到script标签的时候,会加载对应的Javascript代码,加载完成后立即执行,这个过程会将HTML解析阻塞住,那有没有办法解决页面阻塞的问题呢?在HTML5中引入了async和defer这两个属性,可以解决script加载阻塞页面解析的问题。
首先我们看看MDN对这两个属性的解释:
如上,是async属性对script标签的加载和执行的影响:
async为true的script是异步加载和执行的,加载过程不会阻塞浏览器对HTML的解析async为true的script的执行的顺序是不确定的,执行顺序并不是脚本的引用顺序<html>
<head>
<link rel="stylesheet" href="./style.css"></link>
</head>
<body>
<div class="test">Test1</div>
<div class="test">Test2</div>
<div class="test">Test3</div>
<div class="test">Test4</div>
<div class="test">Test5</div>
<script type="text/javascript" async=true src="./app.js"></script>
</body>
</html>
从上面的profile的图可以很明显的看到这个过程,Javascript的加载和HTML的解析是并行的,并不会阻塞HTML的parse,但是javascript代码的执行的实际这里是无法确定的。
<html>
<head>
<link rel="stylesheet" href="./style.css"></link>
</head>
<body>
<div class="test">Test1</div>
<div class="test">Test2</div>
<div class="test">Test3</div>
<div class="test">Test4</div>
<div class="test">Test5</div>
<script type="text/javascript" async=true src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"></script>
<script type="text/javascript" async=true src="./app.js"></script>
</body>
</html>如上,我们在这里从cdn上引入了JQuery的代码,我们来看看这里async=true的时候,对javascript代码的执行情况的影响。
在上面的profile的图中,我们可以很明显的看到,javascript代码的执行顺序,和script的引入顺序并不一致。
我们接着来看看defer属性对script标签的影响:
根据MDN的解释,在defer属性为true的情况下,script标签会异步加载,脚本的执行是按照script标签在HTML中出现顺序执行,等所有的script执行完毕之后才会触发DOMContentLoaded事件。
对于script的加载和执行,结合文中实际的例子,我们总结一下:
script标签默认是同步加载,加载完成之后立即执行async为true的时候,script的加载是异步的(不会阻塞HTML的解析过程),代码的执行顺序不一定是script标签出现的顺序。defer的加载和async是异步的不会阻塞HTML的parse,和async区别在于,defer标签的执行是按照在文档中定义的顺序执行的,在代码执行完毕之后才触发DOMContentLoaded,async的代码的执行并不对DOMContentLoaded有影响(代码的执行可能在DOMContentLoaded事件之后,意味着如果在async为true的脚本中拿不到DOMContentLoaded的事件)]兴趣遍地都是,坚持和持之以恒才是稀缺的