今天在看到菜鸟教程中的
HTML 中的 Javascript 脚本代码必须位于
<script>
与</script>
标签之间。Javascript 脚本代码可被放置在 HTML 页面的<body>
和<head>
部分中
不禁好奇这两者的区别在哪?
这就要谈到浏览器的执行机制
浏览器的执行机制
Critical Rendering Path(CRP关键渲染路径)
是指浏览器获取到如下图1所示文档到屏幕打印出显示内容的过程
图1:浏览器获取到的HTML文件
(可以通过鼠标右键页面后选择“查看网页源代码得到”)
图2:浏览器解析后展示的界面
以上从图1到图2的过程浏览器主要经历了一下五个步骤,如下图3所示:
- parseHTML (构建DOM树)
- parseCSS(构建CSSOM树)
- Render Tree(构建渲染树)
- layout (布局)
- Paint (打印输出)
图3 关键渲染路径步骤
注意:parse HTML 和 parse CSS是同时进行的(这非常重要!!!)
接下来详细分解阐述一下每个步骤,
我感觉整个过程是一场“维密秀”
,怎么说呢(你且听我慢慢虾扯)
1 parseHTML 构建DOM
由上图1可见拿到的HTML文档是很长的 不可能等解析完了再显示页面(会造成长时间的空白页) 所以浏览器只要拿到一部分就开始构建DOM
构建DOM树
的过程如下图4所示:
在网络中传输的都是二进制01代码Bytes
,通过指定解析方法(如UTF-8等)得到characters
,这些字符集会由Tokenier分成一个个的Tokens
,如图5所示,拆分后的Tokens就会变成一个个的结点Nodeds
,最后根据tokens解析的包含嵌套关系生成一棵类似于树状图的DOM树
图4:DOM树的构建过程
解析一下图4:
这个DOM树就相当于是一场维密秀,是离不开台前和幕后的准备的,body
里的就是台前,观众可以看到的舞台呈现内容,而舞台呈现也离不开幕后head
里工作人员的准备工作。
按照浏览器的执行顺序,是先处理head中的内容,也就是先做准备工作,才会保证秀的最终正常展示
图5:Tokenizer解析出Tokens(不同于网络中的令牌token)
2 构建CSSOM
(严格来讲这个地方不应该是2,因为是和1同时进行的)
在有了DOM树后相当于舞台的舞美构造和模特选角就有了
模特是可以一个接一个的走出来(避免冷场,DOM的构建是循序渐进的)
但她们的衣服(CSSOM)一定都是幕后工作人员提前全部准备好了的(完整的CSS文件)
文档会有一些默认css样式属性称为“user agent styles”舞台的舞美
如果是外部样式,CSSOM的构建就必须获得一份完整的CSS文件她们的衣服
不同于DOM的构建是一个循序渐进的过程,因为CSS的样式存在覆盖性,会以最终结果为准,如果我们提前构建就会出错
所以从这里可以看出CSS加载的优先级蛮高的,一般通过link放在head中提前加载完成
(就像女生出门试衣服总是会穿身换套,必须以她出门的最后一套为准)
内联样式 or 没有设置样式 --> css的解析就在parse HTML过程中
外部样式 --> css的解析就在parse CSS过程中
图6:解析CSSOM的过程
3 Render Tree
之前提到过不是所有人都上场,而是只有模特(“可见”)才会走上t台
而幕后人员并不上台(head中的内容)
所以Render Tree的构建过程是
1 浏览器从DOM开始 遍历每一个“可见”结点
2 对于每一个“可见”结点,在CSSOM上找到匹配的样式并引用每个模特有自己对应的服装
3 上面两者对应相结合 生成Render Tree
这个地方会涉及到一个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和浏览器提供的DOMapi
和BOMapi
能够和页面产生一个交互
(我个人会觉得这个背景挺重要的,知其所以然,因为既然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 属性来解决浏览器的阻塞问题,如下图所示:
(浏览器只能解析这三种html css js 也就是入门的时候说的前端三件套,所以在vue中我们写的.vue文件 浏览器是不能解析的,必须通过脚手架这一类的工具转成浏览器能认识的语言)
而JavaScript不只是可以改DOM,它还可以更改样式,也就是它可以更改CSSOM。因为不完整的CSSOM是无法使用的模特服装没有准备好 大秀是没办法开的
,如果JavaScript想访问CSSOM并更改它,那么在执行JavaScript时,必须要能拿到完整的CSSOM。所以就导致了一个现象,如果浏览器尚未完成CSSOM的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟脚本执行和DOM构建,直至其完成CSSOM的下载和构建。也就是说,在这种情况下,浏览器会先下载和构建CSSOM,然后再执行JavaScript,最后在继续构建DOM。
出现link
和style
标签会造成js的阻塞,如下图所示
css 阻塞
<style>
标签中的样式- 由parsing html过程进行解析
- 不阻塞浏览器渲染
- 不阻塞 DOM 解析
<link src='index.css'>
引入的外部 css 样式
(推荐使用<link>
方式引入外部 css,可以避免闪屏现象)-
由parsing css进行解析
-
会阻塞浏览器页面渲染(原因:避免闪屏)
-
不阻塞 DOM 结构的解析
-
会阻塞 js 的执行(但不会阻塞 js 等资源的加载)因为外源css必须拿到完整的文件,所以没有谁能阻塞css的执行
-
优化方案:(尽可能快的提高 css 加载速度)
使用 CDN 加速
对 css 进行压缩(用打包工具,比如 webpack, gulp 等,也可以通过开启 gzip 压缩)
减少 http 请求数,将多个 css 文件合并
- 总结一下
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的构建,直到请求完成才开始执行脚本
参考文章和视频
浏览器的执行机制和渲染机制