今天在看到菜鸟教程中的
HTML 中的 Javascript 脚本代码必须位于
<script>
与</script>
标签之间。Javascript 脚本代码可被放置在 HTML 页面的<body>
和<head>
部分中
不禁好奇放在这两者的区别在哪?其实是从这个小问题入手的,发现这就要谈到浏览器的执行机制
可以理清包括但不仅限于以下问题:
- 键入URL后浏览器发生了什么(不涉及到网络的那一部分)
- js文件加载带来的阻塞,阻塞的解决方案
- Vue虚拟DOM技术的好处
以下内容只是个人理解,要是有错希望大佬们不吝赐教(求生欲极强,主要还是有些没懂的地方)
目录
2 parseCSS 构建styleSheets(CSSOM)
Chrome浏览器的执行机制
Critical Rendering Path(CRP关键渲染路径)是指:
浏览器获取到html文档(如下图1所示)到屏幕打印出显示用户能看到的内容(如下图2所示)这一过程
图1:浏览器获取到的HTML文件(可以通过鼠标右键页面后选择“查看网页源代码得到”)
图2:浏览器解析后展示的界面
从图1到图2的过程(是不是很神奇,此时我直接起身为浏览器鼓掌!!!)
浏览器在这个过程中主要经历了以下几个子步骤:
- 构建 DOM 树
- 计算属性CSSOM树
- 布局阶段Layout
- 分层Layer
- 绘制Paint
- 分块
- 光栅化和合成
图3 关键渲染路径步骤(几年前的渲染步骤)
注意:parse HTML 和 parse CSS其实是同时进行的(非常重要!!!)
接下来详细分别阐述一下每个步骤,(我感觉整个过程是一场“维密秀”
,怎么说呢,你听我慢慢瞎扯)
1 parseHTML 构建DOM树
-
什么是DOM,为什么要构建DOM?如何查看DOM?
DOM全称为“文档对象模型”(Document Object Model)
文档表示的就是整个的HTML网页文档(就刚才图1那一整个html文档内容)
对象表示将网页中的每一个部分都转换为了一个对象。
模型来表示对象之间的关系,这样方便我们获取对象
而浏览器无法直接理解和使用HTML,所以需要将HTML转换为浏览器能够理解的结构—DOM 树
F12按下输入document就可以看到DOM
(打开就可以看到 DOM 和 HTML 内容几乎是一样的,但是和HTML 不同的是,DOM 是保存在内存中的树状结构,可以通过JavaScript来查询或修改DOM,或者 jQuery封装的很多js方法也能操作DOM)
也就是说:输入HTML文件 经过HTML解析器 输出DOM树
- 那G具体是怎么解析的呢?
而由上图1可见浏览器拿到的HTML文档是很长的 不可能等解析完了再显示页面(会造成长时间的空白页),所以浏览器只要拿到一部分就开始构建DOM
,构建DOM树的过程如下图4所示:
图4:DOM树的构建过程
- 在网络中传输的都是二进制01代码
Bytes
通过指定解析方法(如UTF-8等)得到characters
(html的head中有一行<meta charset="UTF-8"/>
)
这些字符集会由Tokenier分成一个个的Tokens
,如图5所示
拆分后的Tokens就会变成一个个的结点Nodeds
,最后根据tokens解析的包含嵌套关系生成一棵类似于树状图的DOM树
页面的呈现就相当于是一场维密秀,是离不开台前和幕后的准备的,body
里的就是台前,观众可以看到的舞台呈现内容,离不开幕后head
里工作人员的准备工作。
按照浏览器的执行顺序,是先处理head
中的内容,也就是先做宣传准备工作(head中的标签如:meta,link,script,style,title),才会保证秀body
的最终正常展示
图5:Tokenizer解析出Tokens(不同于网络中的令牌token)
八股文之
- 对meta标签的了解
- html语义化标签(对语义化的理解)?
<!doctype html>
? 进一步问html5新特性
2 parseCSS 构建styleSheets(CSSOM)
现在我们已经生成 DOM 树了,但是 DOM 节点的样式我们依然不知道,要让 DOM 节点拥有正确的样式,这就需要样式计算了(严格来讲这个地方不应该是2,因为是和1同时进行的)
和 HTML 文件一样,浏览器也是无法直接理解这些纯文本的 CSS 样式,所以当渲染引擎接收到 CSS 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的结构——styleSheets
(F12输入document.styleSheets就可以看到),该结构同样具备了查询和修改功能,这会为后面的样式操作提供基础(可以理解为化妆师JS给模特后台补妆),构建CSSOM的过程如下图6所示
图6:构建CSSOM的过程(其实还存在转换样式表中的属性值,使其标准化的阶段)
注意: 得到了styleSheets之后我们不是马上就应用在DOM树上了,如2em、blue、bold,这些类型数值不容易被渲染引擎理解,所以我们转换样式表中的属性值,使其标准化
,如下图7所示
现在样式的属性已被标准化了,接下来就需要计算 DOM 树中每个节点的样式属性了,如何计算呢?
这就涉及到 CSS 的继承规则和层叠规则了(CSS的层叠规则)
也就是说CSSOM构建的整个过程是:
输入css文件 ---> 经过css解析器整理成styleSheets
--根据css的层叠和继承性--> 输出每个 DOM 节点的样式,并被保存在 ComputedStyle 的结构内 ---> CSSOM
不同于DOM的构建是一个循序渐进的过程,因为CSS的样式存在覆盖性,会以最终结果为准,如果我们提前构建就会出错
在有了DOM树后相当于模特选角就有了模特是可以一个接一个的走出来(避免空白页面冷场,DOM的构建是循序渐进的)但她们的衣服(CSSOM)一定都是幕后工作人员head提前全部准备好了的(完整的CSS文件)
(所以感觉从这里可以看出CSS加载的优先级蛮高的,一般通过link放在head中提前加载完成)
css样式文件的引入有三种方式
- 通过
link
引用的外部 CSS 文件 <style>
标记内的 CSS- 元素的
style
属性内联的 CSS
没有样式或者内联样式--> css的解析就在parse HTML过程中
外部样式 --> css的解析就在parse CSS过程中
八股文之css选择器权重? ,link和@import导入样式的区别? HTML中href、src 区别
3 布局阶段Layout
截至目前我们已经有了DOM树和结点样式styleSheet(或者叫CSSOM?我看有些博客说可以等价,期待一位大佬在评论区解答一下),那么接下来就需要计算出 DOM 树中可见元素的几何位置,我们把这个计算过程叫做布局。
注:要是修改了元素的几何属性(如宽、高、边距),就会从这个阶段重新执行下面的所有流程,这个过程叫做重排
Chrome 在布局阶段需要完成两个任务:创建布局树
和布局计算
3.1创建布局树
不是所有人都上场,而是只有模特(“可见”)才会走上t台而幕后人员并不上台(head,display:none),替补可能上场(visibility:hidden)
八股文之:display相关属性? display:none和visibility:hidden、opacity: 0的区别? opacity和rgba的区别?
所以布局树的构建过程是
- 浏览器从DOM开始 遍历每一个“可见”结点
- 对于每一个“可见”结点,在CSSOM上找到匹配的样式并引用
每个模特有自己对应的服装
- 上面两者对应相结合 生成布局树,如下图所示
现在我们有了一棵完整的布局树(任务一完成,创建布局树)。那么接下来,就要计算布局树节点的坐标位置了(任务二:布局计算)。在执行布局计算的时候,会把布局运算的结果重新写回布局树中
这个地方再复习一下:上面的CSS加载为什么重要
因为布局树依赖于DOM树和CSSOM树,所以必须等到CSSOM树构建完成,也就是CSS资源加载完成
八股文之:CSS的匹配规则是从右往左的 就是为了页面尽快的渲染
考虑如下场景:此时构建了部分DOM,而CSSOM构建是完全完成的,浏览器就会开始构建Render Tree,如果我们找到一条规则从右往左的匹配,我们就只需要逐层观察该节点的父节点是否匹配,而此时的父节点肯定已经在DOM上了,但是反过来我们可能匹配到一个还未出现在DOM上的结点 (ps:想到了LeetCode上的一道题另一棵树的子树 DOM树就是那一颗子树,而CSSOM就是一颗完整的树,要把DOM子树和CSSOM树匹配起来最快该怎么匹配)
3.2 布局计算
布局树构建了之后就知道了页面需要挂载哪些元素,以及挂载元素的样式
接下来就需要知道挂载在哪,也就是计算出相对于视窗(屏幕)的位置和大小
引出CSS盒子模型,每个盒子摆在哪 怎么摆的问题
模特要怎么走,站定的posing是啥
八股文之:css定位属性position? 如何脱离文档流? 常见布局方式(flex,grid,双飞翼,圣杯布局)
4 分层Layer
为什么要分层? 因为页面可能存在3D变换,页面滚动或z-indexing做z轴排序等效果,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树,而这些图层叠加在一起才形成了最终的页面
八股文之:z-index压盖顺序? 过渡与动画?
那图层树(LayerTree)和布局树(LayoutTree)的关系是什么呢? 如果布局树中的某个节点没有对应的图层树,那它就从属于父节点的图层树(有点像对象通过原型链查找属性或方法的概念,只不过这个地方是节点查找自己所从属的图层) 布局树中每一个节点都会直接或者间接地从属于一个层
那满足什么条件可以被单独赋予一个图层呢?
if(拥有层叠上下文属性的元素 || 需要剪裁)
{会为单独的一层}
什么是层叠上下文?
5 图层绘制
以下括号内的内容是自己的理解哈
在完成图层树(LayerTree)的构建后,就开始依次对每个图层(for Layer of LayerTree)进行绘制,而每个图层(for nodes of Layer)也拆分成一步一步的绘制指令放到绘制列表里等待执行
注:如果修改了元素的背景颜色,就会从这个阶段重新执行下面的所有流程,这个过程叫做重绘 相较于重排开销要小得多,因为省去了布局和分层阶段
八股文之:用CSS画一个三角形? 怎么画宽度为0.5px的线?
6 栅格化操作
浏览器主线程会将绘制列表中的操作指令给到(commit)浏览器的渲染进程中的合成线程来完成
注: 当前的浏览器为多进程架构,最新的 Chrome 浏览器包括:1 个浏览器(Browser)主进程、1 个 GPU 进程、1 个网络(NetWork)进程、多个渲染进程和多个插件进程他们各自的工作是什么,是如何分工合作的? 每个进程中管理了哪些线程?(遇到疑问先加粗,挖个坑到时候写了放链接)
合成线程会将先图层划分为图块(256x256 或者 512x512),因为有的图层很大,而视窗(屏幕)可能很小,我们只需要加载视窗附近的区域,也就能避免绘制用户暂时看不到的区域造成额外开销
视窗附近区域所涉及到的图块会由合成线程生成位图,而合成线程也像主线程给它下放任务一样,将图块转换成位图的需求任务下放给栅格化线程。所谓栅格化,是指将图块转换为位图,而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的 (除GPU生成位图时的快速栅格化外)
7 合成和显示
一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”
然后将该命令提交给浏览器进程。浏览器进程接收到合成线程发过来的 DrawQuad 命令
然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。
注:如果实现动画,那么最小的消耗就是采用CSS的transform,这样会从该阶段重新开始,叫做合成
通过浏览器的执行机制,我们就知道了,为什么说我们要尽量减少对DOM的直接操作,因为一旦操作DOM这样所需要重新经历的流程就多了(Vue使用的虚拟DOM技术我好像有点懂了)
阻塞
好了 了解完浏览器的渲染流程后
是不是发现还没出现js的身影? 不要急,因为一开始浏览器就是类似于“电子报纸”的形式,只能阅读html和css写出来的静态页面,但不能交互,后来才出现了与浏览器互动的桥梁——JavaScript
程序媛通过js和浏览器提供的DOMAPI
和DOMAPI
能够和页面产生一个交互(--->jQuery的出现)
(因为我个人会觉得这个背景挺重要的,知其所以然,因为既然JS的出现是为了操作页面,一般提到操作可以总结成“增 删 改”这类的词嘛,那对于DOM来讲能改变页面结构,对于CSSOM来讲能改变页面样式。)
下一坑:浏览器中JS的执行机制
参考文章和视频:
强烈推荐李兵老师的浏览器工作原理与实践 受益匪浅~