javascript放在head和body的区别以及js文件加载带来的阻塞解决

键入URL后浏览器发生了什么

今天在看到菜鸟教程中的

HTML 中的 Javascript 脚本代码必须位于<script></script> 标签之间。Javascript 脚本代码可被放置在 HTML 页面的 <body><head> 部分中

不禁好奇这两者的区别在哪?
这就要谈到浏览器的执行机制


浏览器的执行机制

Critical Rendering Path(CRP关键渲染路径)
是指浏览器获取到如下图1所示文档到屏幕打印出显示内容的过程

图1:浏览器获取到的HTML文件
(可以通过鼠标右键页面后选择“查看网页源代码得到”)
image


图2:浏览器解析后展示的界面
image


以上从图1到图2的过程浏览器主要经历了以下几个子个步骤:

  • 构建 DOM 树
  • 计算属性
  • 布局阶段
  • 分层
  • 绘制
  • 分块
  • 光栅化和合成

图3 关键渲染路径步骤(几年前的渲染步骤)
image

注意:parse HTML 和 parse CSS是同时进行的(这非常重要!!!)

接下来详细分解阐述一下每个步骤,
我感觉整个过程是一场“维密秀”,怎么说呢(你且听我慢慢虾扯)

1 parseHTML 构建DOM树

  • 为什么要构建DOM?
    这是因为浏览器无法直接理解和使用 HTML,所以需要将 HTML 转换为浏览器能够理解的结构——DOM 树
  • 如何查看DOM?
    F12开发者模式下输入document就可以看到(但打开就可以看到 DOM 和 HTML 内容几乎是一样的,但是和 HTML 不同的是,DOM 是保存在内存中树状结构,可以通过 JavaScript 来查询或修改其内容。)

也就是说:输入HTML文件 经过HTML解析器 输出DOM树

由上图1可见拿到的HTML文档是很长的 不可能等解析完了再显示页面(会造成长时间的空白页) 所以浏览器只要拿到一部分就开始构建DOM
构建DOM树的过程如下图4所示:

在网络中传输的都是二进制01代码Bytes,通过指定解析方法(如UTF-8等)得到characters,这些字符集会由Tokenier分成一个个的Tokens,如图5所示,拆分后的Tokens就会变成一个个的结点Nodeds ,最后根据tokens解析的包含嵌套关系生成一棵类似于树状图的DOM树


图4:DOM树的构建过程
image
解析一下图4:
这个DOM树就相当于是一场维密秀,是离不开台前和幕后的准备的,body里的就是台前,观众可以看到的舞台呈现内容,而舞台呈现也离不开幕后head里工作人员的准备工作。
按照浏览器的执行顺序,是先处理head中的内容,也就是先做准备工作,才会保证秀的最终正常展示

图5:Tokenizer解析出Tokens(不同于网络中的令牌token)
image

2 计算属性 构建styleSheets

现在我们已经生成 DOM 树了,但是 DOM 节点的样式我们依然不知道,要让 DOM 节点拥有正确的样式,这就需要样式计算了(严格来讲这个地方不应该是2,因为是和1同时进行的)
image

在有了DOM树后相当于舞台的舞美构造和模特选角就有了

模特是可以一个接一个的走出来(避免冷场,DOM的构建是循序渐进的)

但她们的衣服(CSSOM)一定都是幕后工作人员提前全部准备好了的(完整的CSS文件)

和 HTML 文件一样,浏览器也是无法直接理解这些纯文本的 CSS 样式,所以当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构——styleSheets,并且该结构同时具备了查询和修改功能,这会为后面的样式操作提供基础。

  • 如何查看浏览器能解析的styleSheets?
    F12开发者模式下输入document.styleSheets就可以看到

输入的css样式文件的引入有三种方式

  • 通过link引用的外部 CSS 文件
  • <style>标记内的 CSS
  • 元素的 style 属性内嵌的 CSS

也就是说:输入css文件 经过css解析器整理成styleSheets
--根据css的层叠和继承性--> 输出每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内

得到了styleSheets之后我们不是马上就应用在DOM树上了,如2em、blue、bold,这些类型数值不容易被渲染引擎理解,所以我们转换样式表中的属性值,使其标准化,如下图所示
image

文档会有一些浏览器默认css样式属性称为“user agent styles”舞台的舞美(下图圈3的地方)
如果是外部样式,CSSOM的构建就必须获得一份完整的CSS文件她们的衣服
不同于DOM的构建是一个循序渐进的过程,因为CSS的样式存在覆盖性,会以最终结果为准,如果我们提前构建就会出错
所以从这里可以看出CSS加载的优先级蛮高的,一般通过link放在head中提前加载完成
(就像女生出门试衣服总是会穿身换套,必须以她出门的最后一套为准)

没有样式或者内联样式--> css的解析就在parse HTML过程中

外部样式 --> css的解析就在parse CSS过程中

图6:解析CSSOM的过程(其实还存在转换样式表中的属性值,使其标准化的阶段)
image

现在样式的属性已被标准化了,接下来就需要计算 DOM 树中每个节点的样式属性了,如何计算呢?这就涉及到 CSS 的继承规则和层叠规则了。
image

3 布局阶段

到目前位置我们已经有了DOM树和结点样式,那么接下来就需要计算出 DOM 树中可见元素的几何位置,我们把这个计算过程叫做布局。

Chrome 在布局阶段需要完成两个任务:创建布局树布局计算

image

之前提到过不是所有人都上场,而是只有模特(“可见”)才会走上t台
而幕后人员并不上台(head中的内容)
所以布局树的构建过程是
1 浏览器从DOM开始 遍历每一个“可见”结点
2 对于每一个“可见”结点,在CSSOM上找到匹配的样式并引用每个模特有自己对应的服装
3 上面两者对应相结合 生成布局树
image

现在我们有了一棵完整的布局树。那么接下来,就要计算布局树节点的坐标位置了。
在执行布局计算的时候,会把布局运算的结果重新写回布局树中

  • 如果下载 CSS 文件阻塞了,会阻塞 DOM 树的合成吗?会阻塞页面的显示吗?
    有可能
    ` <head> <style type="text/css" src = "theme.css" /> </head> <body> <p>极客时间</p> <script> let e = document.getElementsByTagName('p')[0] e.style.color = 'blue' </script> </body>
` 当在JavaScript中访问了某个元素的样式,那么这时候就需要等待这个样式被下载完成才能继续往下执行,所以在这种情况下,CSS也会阻塞DOM的解析。

因为render tree依赖于DOM树和cssom树,所以必须等到cssom树构建完成,也就是CSS资源加载完成,这个地方会涉及到一个CSS的匹配规则是从右往左的 就是为了页面尽快的渲染(面试)
body .parent .children {color:pink}
考虑如下场景:此时构建了部分DOM,而CSSOM构建是完全完成的,浏览器就会开始构建Render Tree,如果我们找到一条规则从右往左的匹配,我们就只需要逐层观察该节点的父节点是否匹配,而此时的父节点肯定已经在DOM上了,但是反过来我们可能匹配到一个还未出现在DOM上的结点
ps:想到了LeetCode上的一道题另一棵树的子树
DOM树就是那一颗子树,而CSSOM就是一颗完整的树,要把DOM子树和CSSOM树匹配起来

4 layout

render树构建了之后就知道了页面需要挂载哪些元素,以及挂载元素的样式

接下来就需要知道挂载在哪,也就是计算出相对于视窗的位置和大小

引出盒子模型每个盒子摆在哪 怎么摆的问题

模特要怎么走,站定的posing是啥

5 Paint

浏览器将每个节点以像素显示在屏幕上,最终呈现我们看到的页面

阻塞

好了 了解完浏览器的渲染流程后

是不是发现还没出现js的身影?
不要急,因为一开始浏览器就是类似于“电子报纸”的形式,只能阅读html和css写出来的静态页面,但不能交互,后来才出现了与浏览器互动的桥梁——JavaScript
程序媛通过js和浏览器提供的DOMapiBOMapi能够和页面产生一个交互

(我个人会觉得这个背景挺重要的,知其所以然,因为既然js的出现是为了操作页面,一般提到操作可以总结成“增 删 改”这类的词嘛,那对于DOM来讲能改变页面结构,对于CSSOM来讲能改变页面样式。)

JS的用途就决定了它必须是单线程操作,多线程会产生操作冲突,比如在原本的DOM树上,这个节点node是要显示,结果js同时要删掉他,浏览器就会懵逼:你到底要我干嘛,我到底现不现实 (也就是下面这句话的缘由)

"UI渲染线程" 与 "JS引擎" 是 互斥的,当 JS引擎执行时UI线程会被挂起

也就是说,在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。

所以如果想首屏渲染的越快,就不建议在首屏就加载 JS 文件资源,这也是很多地方说script 标签放在 body 标签底部的原因。当然也不是说外源script标签必须放在底部,因为可以给script标签添加 defer 或者 async 属性来解决浏览器的阻塞问题,如下图所示:

image

(浏览器只能解析这三种html css js 也就是入门的时候说的前端三件套,所以在vue中我们写的.vue文件 浏览器是不能解析的,必须通过脚手架这一类的工具转成浏览器能认识的语言)

而JavaScript不只是可以改DOM,它还可以更改样式,也就是它可以更改CSSOM。因为不完整的CSSOM是无法使用的模特服装没有准备好 大秀是没办法开的,如果JavaScript想访问CSSOM并更改它,那么在执行JavaScript时,必须要能拿到完整的CSSOM。所以就导致了一个现象,如果浏览器尚未完成CSSOM的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟脚本执行和DOM构建,直至其完成CSSOM的下载和构建。也就是说,在这种情况下,浏览器会先下载和构建CSSOM,然后再执行JavaScript,最后在继续构建DOM。
出现linkstyle标签会造成js的阻塞,如下图所示
image

  1. css 阻塞
    • <style> 标签中的样式
      1. 由parsing html过程进行解析
      2. 不阻塞浏览器渲染
      3. 不阻塞 DOM 解析
    • <link src='index.css'> 引入的外部 css 样式
      (推荐使用 <link>方式引入外部 css,可以避免闪屏现象)
      1. 由parsing css进行解析

      2. 会阻塞浏览器页面渲染(原因:避免闪屏)

      3. 不阻塞 DOM 结构的解析

      4. 会阻塞 js 的执行(但不会阻塞 js 等资源的加载)因为外源css必须拿到完整的文件,所以没有谁能阻塞css的执行
        image

优化方案:(尽可能快的提高 css 加载速度)
使用 CDN 加速
对 css 进行压缩(用打包工具,比如 webpack, gulp 等,也可以通过开启 gzip 压缩)
减少 http 请求数,将多个 css 文件合并

  1. 总结一下js 阻塞
    会阻塞 DOM 解析 因为 js 可能会修改 DOM 树
    会阻塞 页面的渲染 因为 js 代码可能会修改 DOM 树 / CSSOM 树 的结构
    js 会顺序执行,阻塞后续 js 逻辑的执行 (不阻塞 js 等其他资源的加载)
    css 的解析 和 js 的执行 是互斥的 ( css 解析的时候 js 停止执行,js 执行的时候 css 停止解析)
    1 解析html构建dom的时候,遇到js会被阻塞(所以我们经常讲把js代码放在最后)
    2 js执行会被CSSOM建构阻塞,也就是说js必须在CSSOM构建后才能去执行(CSSOM的重要性:必须一次性完成建构 且不会被打断)
    CSSOM构建会阻塞js,js会阻塞DOM构建(加了defer这类除外哈~)
    3 如果使用aysnc异步脚本,脚本的网络请求优先级降低,且网络请求期间不阻塞DOM的构建,直到请求完成才开始执行脚本

参考文章和视频
浏览器的执行机制和渲染机制

浏览器渲染经典文章(英语)

写的不错的文章

讲的不错的视频1

讲的不错的视频2

posted @ 2023-02-15 12:20  二马要努力  阅读(439)  评论(0)    收藏  举报