在访问网页的时候,浏览器是如何渲染当前的页面的?浏览器获取到输入(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
的事件)]兴趣遍地都是,坚持和持之以恒才是稀缺的