1. 浏览器进程
1. 浏览器的多进程架构
- 一个好的程序常常被划分为几个相互独立又彼此配合的模块,浏览器也是如此。
- 以 Chrome 为例,它由多个进程组成,每个进程都有自己核心的职责,它们相互配合完成浏览器的整体功能,
- 每个进程中又包含多个线程,一个进程内的多个线程也会协同工作,配合完成所在进程的职责。
- Chrome 采用多进程架构,其顶层存在一个 Browser process 用以协调浏览器的其它进程。
2. 浏览器的主要进程与功能
详见下面的思维导图
2. 浏览器渲染流程
- 解析 HTML 文件,构建 DOM 树,同时浏览器主进程负责下载 CSS 文件
- CSS 文件下载完成,解析 CSS 文件成树形的数据结构,然后结合 DOM 树合并成 RenderObject 树,CSSOM 的解析过程与 DOM 的解析过程是并行的。
- 布局 RenderObject 树 (Layout/reflow),负责 RenderObject 树中的元素的尺寸,位置等计算,计算图层布局
- 绘制 RenderObject 树 (paint),绘制页面的像素信息,绘制图层
- 浏览器主进程将默认的图层和复合图层交给 GPU 进程,GPU 进程再将各个图层合成(composite),最后显示出页面,整合图层成界面
3. 一些题解
1.为什么js是单线程
这是因为 Javascript 这门脚本语言诞生的使命所致! JavaScript 为处理页面中用户的交互,以及操作 DOM 树、CSS 样式树来给用户呈现一份动态而丰富的交互体验和服务器逻辑的交互处理。 如果 JavaScript 是多线程的方式来操作这些 UI DOM,则可能出现 UI 操作的冲突。 如果 Javascript 是多线程的话,在多线程的交互下,处于 UI 中的 DOM 节点就可能成为一个临界资源, 假设存在两个线程同时操作一个 DOM,一个负责修改一个负责删除,那么这个时候就需要浏览器来裁决如何生效哪个线程的执行结果。 当然我们可以通过锁来解决上面的问题。但为了避免因为引入了锁而带来更大的复杂性,Javascript 在最初就选择了单线程执行。
2. 为什么js阻塞页面加载
js能够操作dom,如果在修改dom的时候渲染界面,那么渲染线程先后获得的元素数据可能会不一致,为了防止渲染出现不可预期的结果,GUI渲染线程与js引擎线程互斥,js引擎执行时,GUI线程被挂起,保存在队列中等到js引擎线程空闲时立即被执行,因此如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
3. css加载会造成阻塞吗
dom和cssdom是并行构建的,所以css加载不会阻塞dom的解析,然而,由于 Render Tree 是依赖于 DOM Tree 和 CSSOM Tree 的, 所以他必须等待到 CSSOM Tree 构建完成,也就是 CSS 资源加载完成(或者 CSS 资源加载失败)后,才能开始渲染。因此,CSS 加载会阻塞 Dom 的渲染。
4. 优化策略
1. css样式表优化:
SS 引擎查找样式表,对每条规则都按从右到左的顺序去匹配 eg:
#myList li {}
查找所有的li,并且每次去确认这个li的父元素id是不是mylist
* {
margin:0
padding:0
}
会匹配遍历所有元素
所以,css优化策略为:
- 避免使用通配符,只对需要用到的元素进行选择。
- 关注可以通过继承实现的属性,避免重复匹配重复定义。
- 少用标签选择器。如果可以,用类选择器替代,举个🌰:
错误示范:#myList li{} 课代表:.myList_li {} - 减少嵌套,后代选择器的开销是最大的,我们应该尽量将选择器的深度降到最低(最高不要超过三层),尽可能使用类来关联每一个标签元素。
2.css与js的加载顺序优化
1.css的阻塞
- 必须等到csson树的生成才能与dom合并为renderTree,即便 DOM 已经解析完毕了,只要 CSSOM 不 OK,那么渲染这个事情就不 OK(这主要是为了避免没有 CSS 的 HTML 页面丑陋地“裸奔”在用户眼前)。
- 我们开始解析 HTML 后、解析到 link 标签或者 style 标签时,CSS 才登场,CSSOM 的构建才开始。很多时候,DOM 不得不等待 CSSOM。因此我们可以这样总结:
CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。
- 策略:
- 把css往前放:放在head标签里
- 启用csn实现静态资源加载速度的优化
2. js的阻塞
页面没有css也可以呈现,只不过毫无交互,死气沉沉,如前面的题解所说,js不光会阻塞dom,还会阻塞csson,因为js会对style和dom进行一些操作,那么渲染线程先后获得的元素数据可能会不一致,为了防止渲染出现不可预期的结果,GUI渲染线程与js引擎线程互斥
eg:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>JS阻塞测试</title>
<style>
#container {
background-color: yellow;
width: 100px;
height: 100px;
}
</style>
<script>
var container = document.getElementById("container")
console.log('container', container)
</script>
</head>
<body>
<div id="container"></div>
<script>
var container = document.getElementById("container")
console.log('container', container)
console.log('container bgColor', getComputedStyle(container).backgroundColor)
</script>
<style>
#container {
background-color: blue;
}
</style>
</body>
</html>
三个console的结果是
- 浏览器之所以阻塞,是因为不知道js会做出什么改变,若不阻止,会造成混乱,但是为什么作为程序员,可以确认js的执行时间,是要立马执行,还是需要等页面渲染完毕再执行,假如我们可以确认一个 JS 文件的执行时机并不一定非要是此时此刻,我们就可以通过对它使用 defer 和 async 来避免不必要的阻塞,这里我们就引出了外部 JS 的三种加载方式。
js的三种加载方式
- 正常模式:
<script src="index.js"></script> 这种情况下 JS 会阻塞浏览器,浏览器必须等待 index.js 加载和执行完毕才能去做其它事情。 - async 模式:
<script async src="index.js"></script> async 模式下,JS 不会阻塞浏览器做任何其它的事情。它的加载是异步的,当它加载结束,JS 脚本会立即执行。 - defer 模式:
<script defer src="index.js"></script> defer 模式下,JS 的加载是异步的,执行是被推迟的。等整个文档解析完成、DOMContentLoaded 事件即将被触发时,被标记了 defer 的 JS 文件才会开始依次执行。 从应用的角度来说,一般当我们的脚本与 DOM 元素和其它脚本之间的依赖关系不强时,我们会选用 async;当脚本依赖于 DOM 元素和其它脚本的执行结果时,我们会选用 defer。 通过审时度势地向 script 标签添加 async/defer,我们就可以告诉浏览器在等待脚本可用期间不阻止其它的工作,这样可以显著提升性能。
|