<script>元素
<script>元素定义了6个属性
async:意思是异步,可选。表示不影响其他操作的同时,立即下载脚本。只对外部文件有效。
defer:推迟;延缓,可选。立即下载,但脚本被延迟到文档完全被解析和显示之后再执行。只对外部文件有效。
src:引入的外部文件
type:编写代码使用的脚本语言的内容类型。默认值是text/javascript
charset:表示src引入的代码的字符集。大多数浏览器会忽略它,很少用到
language:已废弃
<script>中的JavaScript代码会被从上至下依次解释。是依次解释,不是执行。
function fn() {
console.log("Hello");
}
以上代码,解释器会解释一个函数的定义,然后将该定义保存在自己的环境中。
在解释器对<script>元素内部的代码求值完毕以前,页面的其余内容不会被浏览器加载或显示。
加载和解析JavaScript都会暂停DOM的构建。
浏览器按照<script>在页面中出现的先后顺序依次解析。第一个<script>元素包含的代码解析完成后,第二个<script>包含的代码才会被解析。
<script src="./test.js">
console.log("DOM say Hello");
</script>
有src引入外部代码后,潜入的代码会被忽略
在<head>标签中放<script>标签,执行的顺序是先让全部JavaScript代码都被下载、解析、执行完成之后,才开始解析<body>元素,只有解析到<body>元素,页面才开始呈现内容。这样设计的理由是,如果DOM树都构建完毕并且都已经渲染好,再去操作DOM,就会造成回流和重绘,造成性能损耗。
如果<script>很多,下载、解析和执行都需要花很长时间,就会导致浏览器页面空白很长时间。
另外,如果JavaScript中对DOM有操作,删除、更新等,都可能因为DOM还没解析而导致操作失败。
为了避免这个问题,可把全部JavaScript引用放到<body>元素的所有页面元素的最后面。这样,在解析JavaScript之前,所有页面的内容都被呈现在浏览器中。用户就能先看到页面,也能在操作DOM时,DOM树已经构建好。
延迟脚本
defer属性会让script标签中的代码被立即下载,而且是新开一个线程下载,并不影响DOM Tree的构建;等到HTML全部解析完,也就是</html>标签结束后,再执行JavaScript,而且是在DOMContentLoaded之前执行;这就既节约了JavaScript下载的时间,也让DOM构建完成在JavaScript执行之前,就算有对DOM节点的操作,也不会出错。
defer能保证<script>按照顺序执行,就是第一个<script>中的代码执行完毕,再执行第二个<script>中的代码。
所以,defer一般用在需要给浏览器性能做优化时、对DOM有操作时,或者对多个<script>引入内容有顺序要求时使用。
异步脚本
async属性也会立即新开一个线程下载<script>内容,不会导致DOM解析阻塞;下载完毕会立即执行里面的JavaScript代码,此时会阻塞DOM执行。而且执行JavaScript的时机不可控,可能在DOM解析完成之后,也可能在DOM解析完成之前;甚至可能在DOMContentLoaded之后执行。
因为执行JavaScript的时机不可控,所以如果有操作DOM,就有可能失败。而且不会依次执行<script>,在使用时,可用于不需要对DOM和其他script有依赖的情况。
小结
<script>放在head中,会先下载、解析、执行JavaScript,再构建DOM。设计理由是:如果先构建DOM树,并进行了渲染,再执行JavaScript时,如果再操作DOM,就会造成回流和重绘,降低性能。但如此设计也可能造成两个问题:
- JavaScript过大,下载执行时间过长,导致阻塞DOM构建,页面空白持续时间过长;
- JavaScript中对DOM有操作,而DOM还没构建,就会造成操作失败;
为了解决这两个问题,建议把所有<script>放在body的最后面。
- 先构建了DOM,遇到
<body>就会呈现内容,就算JavaScript再大,都会先看到界面; - 先构建了DOM,再操作DOM,没问题
除了放到body的最后面,可以加入defer延迟属性。新线程下载JavaScript,不阻塞DOM构建,提升了性能;DOM构建完毕,再执行JavaScript,既保证了操作DOM能生效,又因为先构建DOM和下载不阻塞而减少页面空白时间;一举三得(性能优化、可操作DOM、减少页面空白时间)
添加async也能立即下载JavaScript,又不会阻塞DOM的构建;但不同于defer,async会让JavaScript在下载后立即执行,而且执行时会阻塞DOM的构建;相比defer,async也能提升性能,即节约JavaScript的下载时间,也会缩短页面空白时间;但是如果操作DOM就可能导致失败,因为DOM可能还没构建好;
综上,如果引入的内容没有顺序要求,也没有对DOM的依赖,就可以用async;如果对DOM有操作,或者多个<script>有顺序要求,就用defer。async和defer都是新线程下载JavaScript,节约了下载时间,加快了页面加载速度,提升页面性能。
下面放了一张对比图