上来先给一个问题:在书写html页面时,当你要从外部引入js文件时,script标签会放置在哪个位置呢,放置位置不同对页面加载有影响吗?
默认情况下,浏览器是同步加载 JavaScript 脚本:即渲染引擎遇到 script 标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。
如果脚本体积很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户会感觉到浏览器“卡死”,出现短暂的空白,没有任何响应。这会造成很不好的用户体验,解决这个问题有两种方案:
①. 改变script标签的放置位置。最好将其丢在body标签的最后面,即放在</ body>标签的前面,这种方式不会影响浏览器的DOM渲染,会让页面处理执行完后才去执行它
②. 同步转异步。浏览器允许脚本异步加载,这种方式可以让 script 标签继续放在head头部,下面是两种异步加载的语法:
<script src="./1.js" async></script> <script src="./1.js" defer></script>
async与defer
异步就是 script 标签打开defer或async属性,脚本就会异步加载。浏览器渲染引擎遇到这一行命令,就会开始下载外部脚本,在下载的同时渲染引擎会直接执行后面的命令。
async和defer都会让外部脚本下载时,渲染引擎不停下来。
async属性与defer的区别就在于:
蓝色线代表网络读取(脚本下载),红色线代表执行,这俩都是针对脚本的;绿色线代表 HTML 解析。
defer属性,浏览器会立即下载相应的脚本,在下载的过程中页面的处理不会停止,等到文档解析完成后脚本才会执行。
async属性,浏览器会立即下载相应的脚本,在下载的过程中页面的处理不会停止,下载完成后立即执行,执行过程中页面处理会停止。
若不设置任何属性,那么当与到script脚本时,会等script脚本下载和执行都完成之后才继续执行下面的页面处理。
【注】且async比defer更“厉害”,当同一标签同时使用两种属性时,遵循async!!!
多个脚本
async和defer的区别不仅体现在外部script文件的下载和执行上,更体现在多个脚本存在时的不同:
先来个代码示例:
外部script文件
1.js 文件:
// ... 非常多的js代码 console.log('1');
2.js 文件:
console.log('2');
主html文件
使用defer:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>js阻塞</title> <!-- defer会让dom先执行 --> <script src="./1.js" defer></script> <script src="./2.js" defer></script> </head> <body> <h1>js的阻塞是如何进行的?</h1> <script> document.addEventListener('DOMContentLoaded', function() { console.log('DOMContentLoaded'); }) </script> </body> </html>
控制台执行结果:
使用async:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>js阻塞</title> <!-- defer会让dom先执行 --> <script src="./1.js" async></script> <script src="./2.js" async></script> </head> <body> <h1>js的阻塞是如何进行的?</h1> <script> document.addEventListener('DOMContentLoaded', function() { console.log('DOMContentLoaded'); }) </script> </body> </html>
控制台执行结果:
从控制台的运行结果就可以看出:
defer:第一个延迟脚本会先于第二个延迟脚本执行(当然,如果一些浏览器不完全遵照html5标准,也会出现不按顺序执行的情况),且这两个脚本会先于DOMContentLoaded事件执行。
async:哪个先下载完成哪个就立即执行!!!这两个脚本不一定会在DOMContentLoaded事件触发之前执行,但一定会在window.onload 事件之前执行完成。另外,值得注意的是,在先下载完的脚本执行过程中,其他脚本不会停止下载,而会继续下载。
【注】DOMContentLoaded会在dom加载完成后触发,即文档完全加载和解析之后触发。
○ 更多关于DOMContentLoaded,请参考 https://www.jb51.net/article/222345.htm
小结
- 防堵塞的最佳实践就是将script标签放到body的最下面;
- defer 和 async 在网络读取(下载)方面是一样的,都是异步的(相较于 HTML 解析)
它俩的差别在于脚本下载完之后何时执行,显然 defer 是最接近我们对于应用脚本加载和执行的要求的,尤其对于有script文件之间有依赖的情况(依赖就是可能这个js文件引用了上一个js文件的内容),它是按照加载顺序执行脚本的!!! - async 则是一个乱序执行的主,对它来说脚本的加载和执行是紧紧挨着的,所以不管你声明的顺序如何,只要它加载完了就会立刻执行
仔细想想,async 对于应用脚本的用处不大,因为它完全不考虑依赖(哪怕是最低级的顺序执行),不过它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说却是非常合适的,最典型的例子:Google Analytics - defer/async都只适合与操作外部脚本,另外,在操作DOM脚本时最好不要使用async/defer,因为比如使用async时可能页面还没加载完,就执行了js代码,这样很可能产生异常;如果一定要使用,可以把需要操作 DOM 的js 部分放在 DOMContentLoaded 事件回调中执行!
参考
[1] https://blog.csdn.net/mx18519142864/article/details/82021754
[2] https://blog.csdn.net/weixin_42561383/article/details/86564715
[3] https://segmentfault.com/q/1010000000640869
到此这篇关于JavaScript 文件加载与阻塞问题之性能优化案例详解的文章就介绍到这了,更多相关JavaScript 文件加载与阻塞问题内容请搜索NICE源码以前的文章或继续浏览下面的相关文章希望大家以后多多支持NICE源码!