在2022年10月24日到11月11日的三个星期中,我参与了大约25家公司的面试并整理出《一份历时三个星期的前端面经请查收》一篇帖子,地址如下:juejin.cn/post/716513…
本篇是针对HTML&CSS部分:2. script标签放在body或者header中会阻塞吗 所进行的答案整理,供大家参考,欢迎大佬们指正~
先说结论:默认情况下,script标签是同步执行,会发生阻塞,但是我们可以通过script的扩展属性async或defer,实现脚步的异步执行或者延迟执行。
一、默认情况(默认脚本)
script标签在默认情况下是同步执行的,也就说浏览器解析html文件时,自上而下解析到script标签时会暂停DOM构建,在脚本加载并执行完毕后,才会继续向下解析。
该方法下,script阻塞了浏览器对html的解析,如果获取js脚本的网络请求迟迟得不到响应,或者js脚本执行时间过长,都会导致白屏,用户看不到页面内容。
二、使用async属性(异步脚本)
此时script标签会异步进行加载,加载脚本时不会影响DOM的构建,但是脚本一旦加载完毕,就会立即开始执行,此时如果DOM还没构建完成,则会暂停DOM构建,直到脚本执行完毕。当然,如果异步脚本下载回来的时候,DOM已经构建完成,那么该脚本对html就没啥影响了,下载完直接执行就好了。
该方法下,因为无法确定js的下载速度,从而导致js的执行时机是不可控的。如果存在多个script async时,它们之间的执行顺序也是不可控的,取决于各自的下载速度,先下载完成的就会被先执行。
三、使用defer属性(延迟脚本)
此时script标签会并行加载,和延迟执行,script脚本的下载和html解析时并行的,script脚本的执行则是在html解析完成之后。
该方法下,html解析器在解析过程中如果遇到script defer标签,则会异步请求脚本,不会阻塞html的解析。在脚本下载好后,如果html还没有解析完成,也不会阻塞html解析,而是会等html解析完成后再执行。如果存在多个script defer标签时,他们之间的执行顺序会按他们在HTML文档中的顺序来进行,这样能够保证JS脚本之间的依赖关系。
补充:html、css、js三者的加载顺序
我们知道一个页面通常由html、css、js三部分组成,一般我们会把css文件放在head头部加载,而js文件则放在页面的最底部加载,想要知道为什么大家都会不约而同的按照这个标准进行构建页面,必须先得了解页面的加载过程。
1、三种情况
我们先来考虑以下三种情况:
1)页面中只包含外部的css文件:我们知道构建布局树需要DOM树和CSSOM树,因此我们需要获得构建这两个树的数据。浏览器在请求回html文件后,html解析器开始对文件数据进行解析,但是由于此时有外部css文件,它就需要先去请求css数据从而构建cssom树。所以,这种情况下,css没有阻塞dom树的构建,只是因为渲染页面的布局树需要cssom树,从而导致了页面的渲染的阻塞。
2)页面中包含内联js和外部css:由于js可能会更改dom,html解析器在构建dom树过程中如果遇到到js,就会停止构建dom,先去解析执行js。但是在执行JS脚本之前,如果页面中包含外部CSS或内联CSS,会先将CSS构建成CSSOM,再去执行JS。这也就是上面说到的为什么一般将JS文件放在页面的最底部的原因。所以,从这种情况来看,js会阻塞dom的构建,css会阻塞js的执行,但它们不会阻塞HTML的解析。因此css和js都会阻塞dom的构建
3) 页面中包含外部js与外部css:HTML解析器在解析过程中如果遇到外部CSS与外部JS文件,就会同时发起请求对文件进行下载,这个过程DOM构建的过程会停止,需要等CSS文件下载完成并构建完CSSOM,JS文件下载完成并执行结束,才会开始构建DOM。我们知道CSS会阻塞JS的执行,所以JS必须要等到CSSOM构建完成之后再执行。所以,这种情况同第二种,js阻塞dom的构建,css阻塞js的执行,从而必须要css下载完成并构建完cssom,才会开始js文件执行,并在js执行完成后,最终开始继续构建DOM。
2、三者的关系梳理
我们来梳理一下三者的关系: 1)css与dom:css不会阻塞dom的解析,但会阻塞dom的渲染。原因是dom和cssom通常并行构建,但是render树依赖dom和cssom树,必须等到两者加载完毕才能开始进行构建渲染。 2)css与js:css会阻塞js执行,但不会阻塞js文件的下载。原因是js可以操作dom和css,如果修改属性同时渲染页面,那么渲染线程前后获得的数据可能不一样,为了防止渲染出现不可预期的结果,浏览器设置了GUI渲染线程与JavaScript线程为互斥的关系,并且因为js脚本是可以获取元素样式的,那么它就必然的依赖css,因此css必须要js执行前加载执行完毕。 3)js与dom:js会阻塞dom的解析,因此也就会阻塞dom的渲染。浏览器设置了GUI渲染线程与JavaScript线程为互斥的关系,当js引擎执行时,GUI线程会被挂起,GUI更新会被保存在一个队列中等到引擎线程空闲时立即被执行。因此如果js执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
3、优化css和js加载
1)css方面:使用cdn,压缩css,合理使用缓存,合并多个css文件从而减少http请求数,或者直接使用内联样式。
2)js延迟加载:如果脚本是模块化,并且无依赖关系,可以考虑使用async。如果脚本直接有依赖关系,则考虑使用defer。如果脚本内容比较小,并且被一个异步脚本依赖,考虑使用默认脚本。