基础概念:
- CPU
中央处理器,串行地一件接一件的处理交给他的任务。cpu 在现代电脑一般有多个核心,例如 8 core, 4 core。核心指的是物理上的概念 - CPU
图形处理器。单个 gpu 只能处理简单的任务,但是数量特别的多!!!并行计算能力非常强
3.进程 & 线程
进程 —— 可以把他看做一个被执行的应用程序。
线程 —— 一个进程包含一个或多个线程。
pid —— processid 进程id
当执行一个应用程序的时候,操作系统会为此程序分配一个进程,会有一块独立的内存空间
当进程结束的时候,此内存空间会被释放掉
浏览器架构
Brooser Process (一个) -> 浏览器进程,负责浏览器主题部分,包含导航栏、标签、前进/后退按钮
Network Process (一个) -> 网络进程,负责页面的网络资源加载,
GUP Process (一个) -> 图像渲染进程,不同的tab是有不同的渲染进程,gpu需要接收这些进程的请求并且绘制页面,因此需要单独做一个进程
Renderer Process (多个) -> 渲染进程,负责标签页内和网页展示相关的工作, 比如将请求得到的 html css js 转换为用户可以交互的网页。默认情况下每个tab有一个独立的渲染进程
Plugin Process(多个) -> 插件进程
其它的进程:工具进程、辅助框架
多进程架构的好处是什么?
- 容错性
例如:chrome 打开了4个tab页,那么 chrome 就为此开了至少4个渲染进程,当我们其中一个tab页崩溃的时候,其它的3个tab就不会有影响。而如果4tab页都子啊一个进程上,那么只要其中一个崩溃了,其他的也会崩溃掉。
- 安全性和沙盒性
浏览器不希望用户可以拥有太多修改底层逻辑的能力,而用户的代码其实是跑在渲染进程里的,所以浏览器需要限制渲染进程的能力。 如果浏览器没有分成多进程架构的话,那么所有的浏览器的能力都放在了一个进程里, 那么这个时候用户的代码也会在同一个进程中跑,用户就可以得到修改底层逻辑的能力;而如果分成多进程架构的时候,浏览器只分配给渲染进程渲染的能力,那么用户也就只能去修改渲染相关的东西,而修改不了底层逻辑。
- 每个进程可以拥有更多的内存
因为每个进程都会分配到一块独立的内存空间
多进程架构的坏处是什么?
- 每个进程都会有一块独立的内存空间
我们的代码跑在 V8 的js引擎里,而每个tab页都有一个自己的渲染进程,也就意味着每一个tab页都有一个v8引擎在消耗不必要的内存。简单来说就是:有些公共的东西,会在不同的进程内存空间中存在,就消耗了不必要的内存。
- Chrome怎么优化上述情况?
- chrome会限制启动的进程数目:当启用的进程数达到上限后,访问同一域名的tab都会放在一个进程里。这样既可以保证消耗了更少的内存,又保证了访问的不同域名网站的隔离。
- chrome的服务化:将某些大的服务拆分成小服务模块,当遇到性能好的设备时,每个服务可以有单独的进程,保证系统的稳定性;当遇到性能不好的设备时,这些服务将会集中在一个进程里,减少内存的消耗。
即使Chrome做了这些优化,但是还是能感受到启动chrome浏览器的时候,内存的消耗还是挺大的。 例如启动窗口或者tab页过多的时候。
网站隔离
我们知道每一个tab页都会有单独的渲染进程,当tab页中加载了一个与tab页不同域名的iframe,如果这个时候还是只有tab页原有的渲染进程,意味着不同域名的iframe与tab页共享同一块内存,这是一个很危险的行为。 同域名策略时浏览器核心的安全模型,如果绕过浏览器的安全策略,则基本上意味着用户直接在互联网上裸奔。 所以,当一个tab页内存在不同域名的iframe,那么浏览器会给这个iframe分配单独的渲染进程。
导航时究竟都发生了什么?
-
url解析,分析是不是一个url
-
如果解析出来是一个url,那么就会针对url上的域名进行dns查询
-
进行dns查询后找到了用户的ip,建立tcp连接
-
发送http请求
-
接收http响应请求
-
页面渲染
a. 解析html, 构建dom树
b. 解析css,构建css规则树
c. 合并dom树和css规则树,生成渲染树render树
d. 布局render树
e. 绘制render树
f. 浏览器将各层的信息发送给GPU,GUP将各层合成,显示在屏幕上
从浏览器原理上看:
1. 处理输入
用户在地址栏输入url的时候,UI线程判断输入的内容是关键词还是url
2. 开始导航
当用户按下回车的时候, UI线程会通知网络线程来初始化一个网络请求,来获取站点内容
如果这个网络请求返回了一个重定向的状态码 301,那么网络进程会通知UI进程进行重定向,然后再发起一个网络请求。
DNS查询在哪进行?-----网络进程,在收到UI进程的通知后开始做的
3. 读取响应
- 响应类型的判断
可以通过哪些字段判断?常见的 Content-Type: application/json代表着返回体的类型为json数据, 但是有时候可能会不支持Content-Type这样一个响应头,那么他就会检查当前接收的这个http响应流的前几个字节来确定当前具体的类型。
- 不同响应类型的处理
- html: 浏览器会将获取到的数据交给渲染进程
- 压缩文件/其它类型: 会交给下载管理器来处理
- 安全检查
比如检查是否和已有病毒页面匹配、或者检查是否是跨站数据 ,如果存在,则拦截下来,不会交给渲染进程
4.寻找一个渲染进程来绘制页面
在网络请求的过程中,UI线程会提前创建一个渲染进程做准备。如果一切顺利,那么直接用这个渲染进程工作;如果发生重定向或者安全问题,那么准备好的这个渲染进程将会被舍弃掉,重新开启一个新的渲染进程。
5. 提交导航
这个时候数据准备好了,渲染进程也准备好了。这个时候文档的加载阶段也正式开始。
6.加载完成
这个时候渲染进程就会进行自己的工作:加载资源、渲染页面。当渲染进程完成渲染,会通过IPC告诉浏览器进程,让UI线程停止导航栏/tab页上的loading提示。
渲染进程中具体做了什么?
1. 构建dom
渲染进程接收html文本数据,并且把他转换为dom对象。
渲染进程如何将HTML文本转换为dom对象?
Hi! <b> I'm <i> chrome </b> ! </i>
// 解析错误吗?不会,浏览器会自动补全解析成如下
Hi! <b> I'm <i> chrome </i> </b> <i> ! </i>
2.加载子资源
比如图片、css、js脚本资源,渲染进程的主进程会在构建dom树的时候,按照引用资源的顺序,一个一个的发送网络请求。
js会阻塞html的解析过程吗?
会!会转向js代码的加载解析和执行。
为什么?因为js代码里可能会重写:document.write(''),如果不先加载解析和执行js代码,那么当解析加载完html之后,经过js代码的重写,整个dom树就会发生改变,那么先前的解析加载工作可以说是做了无用功。所以浏览器规定了:html解析一定要等js解析完成之后才能进行下一步的解析工作。
3.样式计算
渲染进程的主线程会解析页面的css来确定每个dom节点的计算样式computed style
4.布局 layout
- 主线程会遍历刚刚构建的 dom 树,根据dom节点的计算样式来计算出一个布局树
- 布局树上的每个节点会有它在页面上的x,y坐标,以及盒子大小的具体信息。
布局树和dom树的区别: 布局树只会展示那些可见节点,那些不可见的节点是不展示的,比如某个节点设置了
display: none,那么这个节点就不会在布局树上,如果设置visibility: hidden,那么还是会在布局树上。
延伸知识:所谓的布局就是回流\重排,那什么操作会触发回流或者重排 ?
- 添加或者删除可见dom元素
- 已存在元素的位置、尺寸发生改变
- 内容发生改变,例如原本渲染的2 * 2的图片,后来被一张10 * 10的图片给替代了
- 页面最开始渲染、布局的时候(这是不可避免的)
- 浏览器的窗口尺寸发生改变
- 获取一些特殊属性值的时候也会触发 ,比如offsetTop、scrollTop
为什么获取一些特殊属性值也会触发回流?
offseTop、scrollTop、getComputedStyle
由于每次重排/回流都会造成额外的消耗,所以大多数浏览器都会通过队列化修改来批量执行优化重排过程,意思就是说浏览器会把会触发回流/重排的操作存放到一个队列里。当我们进行获取offseTop这些布局信息的操作时,浏览器会默认返回给我们最新的数据,也就意味着浏览器会强制清空上述队列,触发回流/重排来返回最新正确的值。
5.绘制
渲染流水线操作:样式-----》布局 -----》绘制
由上图可以发现,如果布局发生改变,那么绘制就会发生改变。回流一定引发重绘,但重绘不一定引发回流。
js代码阻塞渲染流水线
如图:我们可以把每一个Frame看做动画效果的每一帧,如果没有中间的js,那么就会让整个动画效果看起来很流畅;而如果在渲染主线程中加入了跨域几个帧数的js,那么就会让整个动画效果看起来有定卡顿。
那如何让这种阻塞效果可以消失呢?我们可以让js代码在每一帧里面去执行(切割js代码分到每一帧里去),以此达到不阻碍渲染流水线的进程。
简单问题
一、地址栏输入一个url开始, 浏览器都做了什么工作?
-
URL解析 首先判断你输入的是一个合法的URL 还是一个待搜索的关键词,并且根据你输入的内容进行对应操作
-
DNS查询
具体流程查看 - assets/question-dns.png
- TCP连接
在确定目标服务器服务器的IP地址后,则经历三次握手建立TCP连接
- 发送 http 请求
当建立tcp连接之后,就可以在这基础上进行通信,浏览器发送 http 请求到目标服务器
请求的内容包括:
请求行 请求头 请求主体
- 收到 http 响应请求
当服务器接收到浏览器的请求之后,就会进行逻辑操作,处理完成之后返回一个HTTP响应消息,包括:
状态行 响应头 响应正文
在服务器响应之后,由于现在http默认开始长连接keep-alive,当页面关闭之后,tcp链接则会经过四次挥手完成断开
- 页面渲染
当浏览器接收到服务器响应的资源后,首先会对资源进行解析:
- 查看响应头的信息,根据不同的指示做对应处理,比如重定向,存储cookie,解压gzip,缓存资源等等
- 查看响应头的 Content-Type的值,根据不同的资源类型采用不同的解析方式
关于页面的渲染过程如下:
- 解析HTML,构建 DOM 树
- 解析 CSS ,生成 CSS 规则树
- 合并 DOM 树和 CSS 规则,生成 render 树
- 布局 render 树( Layout / reflow ),负责各元素尺寸、位置的计算
- 绘制 render 树( paint ),绘制页面像素信息
- 浏览器会将各层的信息发送给 GPU,GPU 会将各层合成( composite ),显示在屏幕上
二、js会阻塞HTML的解析过程吗? 为什么?
当HTML解析器碰到script标签的时候,它会停止HTML文档的解析从而转向JavaScript代码的加载,解析以及执行。
为什么要这样做呢?因为script标签中的JavaScript可能会使用诸如document.write()这样的代码改变文档流(document)的形状,从而使整个DOM树的结构发生根本性的改变。因为这个原因,HTML解析器不得不等JavaScript执行完成之后才能继续对HTML文档流的解析工作。
三、什么情况下会触发回流/回流? 浏览器本身有什么优化操作吗?
回流这一阶段主要是计算节点的位置和几何信息,那么当页面布局和几何信息发生变化的时候,就需要回流,如下面情况:
- 添加或删除可见的DOM元素
- 元素的位置发生变化
- 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
- 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代
- 页面一开始渲染的时候(这避免不了)
- 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
还有一些容易被忽略的操作:获取一些特定属性的值
offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight
这些属性有一个共性,就是需要通过即时计算得到。因此浏览器为了获取这些值,也会进行回流
除此还包括getComputedStyle方法,原理是一样的。
浏览器本身有什么优化操作?
由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列
当你获取布局信息的操作的时候,会强制队列刷新,包括前面讲到的offsetTop等方法都会返回最新的数据
因此浏览器不得不清空队列,触发回流重绘来返回正确的值
四、如何尽量避免回流的出现?
- 如果想设定元素的样式,通过改变元素的 class 类名 (尽可能在 DOM 树的最里层)
- 避免设置多项内联样式
- 应用元素的动画,使用 position 属性的 fixed 值或 absolute 值(如前文示例所提)
- 避免使用 table 布局,table 中每个元素的大小以及内容的改动,都会导致整个 table 的重新计算
- 对于那些复杂的动画,对其设置 position: fixed/absolute,尽可能地使元素脱离文档流,从而减少对其他元素的影响
- 使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘
- 避免使用 CSS 的 JavaScript 表达式
- 需要获取offset等属性的时候, 最好用一个变量缓存起来, 不要每次都去获取
五、浏览器多进程架构的好处是什么?
- 容错性
Chrome会为每个tab单独分配一个属于它们的渲染进程(render process)。举个例子,假如你有三个tab,你就会有三个独立的渲染进程。
当其中一个tab的崩溃时,你可以随时关闭这个tab并且其他tab不受到影响。可是如果所有的tab都跑在同一个进程的话,它们就会有连带关系,一个挂全部挂。
- 安全性和沙盒性
因为操作系统可以提供方法让你限制每个进程拥有的能力,所以浏览器可以让某些进程不具备某些特定的功能。例如,由于tab渲染进程可能会处理来自用户的随机输入,所以Chrome限制了它们对系统文件随机读写的能力。
- 每个进程可以拥有更多内存
因为每个进程都会分配一块独立的内存空间, 所以理所当然的, 每个进程都会有更多的内存。
z-index的生效规则
- 只有定位元素才会生效
z-index 这个属性并不是在所有的元素上都有效果。它仅仅只在定位元素(定义了 position 属性,且属性的值为非 static 值的元素)上有效果
- 层叠顺序
2.1 background/border —— 形成层叠上下文的元素的背景和边框,也是层叠上下文中的最低等级。
2.2 z-index <0 —— 层叠上下文内有着 负z-index值 的子元素。
2.3 block块级盒 —— 文档流中非行内非定位子元素。
2.4 float浮动盒 —— 非定位浮动元素。
2.5 inline/inline-block行内盒 —— 文档流中行内级别非定位子元素。
2.6 z-index: 0 —— 定位元素,这些元素将形成了新的层叠上下文。
2.7 z-index > 0 —— 定位元素。 层叠上下文中的最高等级。
所有的元素层级是按照上面的排序进行的, 也就是说z-index > 0的层级最高, 最靠近屏幕前的我们, 也就是观察者.
- 同级比较
有的时候我们发现z-index9999 居然比 z-index 100更低? 为什么??
这时候我们要知道z-index是同级比较, 比如下面的例子
div1 z-index:10
div2 z-index:200
div3 z-index:999
div4 z-index:20
展示的层级高度是怎样的顺序?
是div4 > div3 > div2 > div1
因为div1 和 div4是同级的, div4比整体的div1高.
完整的写一下高度, 效果类似于:
div1: 10
div2: 10-200
div3: 10-999
div4: 20