1.进程?线程?
进程
运行状态的程序需要有自己专属的内存空间,这块空间可以理解为进程
每个应用至少有一个进程,进程之间相互独立,即使需要通信,也要经过双方进程的同意
线程
有了进程之后,就可以运行程序的代码了。运行代码的单位,称之为‘线程’
一个进程至少有一个线程,所以当开启进程之后会自动创建一个线程来执行代码,这个线程称之为‘主线程’,如果程序需要同时执行多块代码,主线程就会启动更多线程来执行代码,所以一个进程可以拥有多个线程
2.浏览器有哪些进程和线程
浏览器是一个多进程多线程的应用程序,为了避免相互影响,减少连环崩溃的概率,当启动浏览器后,会自动启动多个进程
其中最主要的进程有:
浏览器进程
主要负责界面显示、用户交互、子进程管理等。浏览器进程内部会启动多个线程处理不同的任务
网络进程
负责加载网络资源。网络进程内部会启动多个线程来处理不同的网络任务
渲染进程:
渲染进程启动后,会开启一个渲染主线程,主要负责执行 HTML、CSS、JS 代码。默认情况下,浏览器会为每一个标签页开启一个渲染进程,以保证每个标签页之前相互独立
3.事件循环机制
渲染主线程需要处理许多任务,任务调度是一个难题,因此渲染主线程使用‘排队’策略来解决这个难题,此过程则称为“事件循环”
DANGER
- 在最开始的时候,渲染主线程会进入一个无限循环
- 每一次循环会优先检测微队列中是否有任务存在。如果有,就取出第一个任务执行,执行完一个进入下一次事件循环;若微队列为空,则检测其他队列是否存在任务,浏览器自行决定取哪一个队列的任务执行;如果没有,则进入休眠状态
- 其他所有线程(包括其他进程的线程)可以随时向各个消息队列添加任务。新任务会加入到各队列的末尾,在添加新任务时,如果主线程时休眠状态,则会唤醒以继续循环拿取任务
4.如何理解 JS 的异步
首先 JS 是一门单线程语言,这是因为他运行在浏览器的渲染主线程中,而渲染主线程只有一个。渲染主线程承担着许多工作,例如渲染页面、执行 JS 代码
WARNING
如果使用同步的方式,就极有可能导致主线程产生阻塞,从而导致消息队列中的很多其他任务无法得到执行。这样一来,一方面会导致繁忙的渲染主线程白白消耗时间,另一方面导致页面无法及时更新,给用户造成卡死现象
所以浏览器采用异步的方式来避免。具体做法是当某些任务发生时,比如计时器、网络、事件监听,主线程将任务交给其他线程去处理,自身立即结束任务的执行,转而执行后续代码。当其他线程完成时,将事件传递的回调函数包装成任务,加入到消息队列的末尾排队,等待主线程调度执行
在这种异步模式下,浏览器永不阻塞,从而最大限度的保证了单线程的执行流畅
5.浏览器是如何渲染页面的?
当浏览器的网络线程接收到 HTML 文档后,会产生一个渲染任务,并将其传递给渲染主线程的消息队列。在事件循环机制的作用下,渲染主线程取出消息队列的渲染任务,开启渲染流程。渲染流程分为:
过程详情
(1) HTML 解析
解析过程中遇到 CSS 解析 CSS,遇到 JS 执行 JS。为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程,率先下载 HTML 中的外部 CSS 文件和外部的 JS 文件
如果主线程解析到 link 位置,此时外部的 CSS 文件还没有下载解析好,主线程不会等待,继续解析后续的 HTML。这是因为下载和解析 CSS 的工作是在预解析线程中进行的。这就是 CSS 不会阻塞 HTML 解析的根本原因
如果主线程解析到 script 位置,会停止解析 HTML,转而等待 JS 文件下载好,并将全局代码解析执行完成后,才能继续解析 HTML。这是因为 JS 代码的执行过程可能会修改当前的 DOM 树,所以 DOM 树的生成必须暂停。这就是 JS 会阻塞 HTML 解析的根本原因
第一步完成后,会得到 DOM 树和 CSSOM 树,浏览器的默认样式、内部样式、外部样式、行内样式均会包含在 CSSOM 树中
(2) 样式计算
主线程会遍历得到的 DOM 树,依次为树中的每个节点计算出它最终的样式
在这一过程中,很多预设值会变成绝对值,比如 red 会变成 rgb(255,0,0);相对单位会变成绝对单位,比如 em 会变成 px
这一步完成后,会得到一棵带有样式的 DOM 树
(3) 布局
布局阶段会依次遍历 DOM 树的每一个节点,计算每个节点的几何信息。例如节点的宽高、相对包含块的位置;大部分时候,DOM 树和布局树并非一一对应
比如 display:none 的节点没有几何信息,因此不会生成到布局树;又比如使用了伪元素选择器,虽然 DOM 树中不存在这些伪元素节点,但它们拥有几何信息,所以会生成到布局树中。还有匿名行盒、匿名块盒等等都会导致 DOM 树和布局树无法―—对应
(4) 分层
主线程会使用一套复杂的策略对整个布局树中进行分层
分层的好处在于,将来某一个层改变后,仅会对该层进行后续处理,从而提升效率。 滚动条、堆叠上下文、transform、opacity 等样式都会或多或少的影响分层结果,也可以通过 will-change 属性更大程度的影响分层结果
(5) 绘制
主线程会为每一层单独产生绘制指令集,用于描述这一层的内容该如何画出来
(6) 分块
完成绘制后,主线程将每个图层的绘制信息提交给合成线程,剩余工作将由合成线程完成
合成线程首先对每个图层进行分块,将其划分为更多的小区域。它会从线程池中拿取多个线程来完成分块工作
(7) 光栅化
分块完成后,进入光栅化阶段
合成线程会将块信息交给 GPU 进程,以极高的速度完成光栅化
GPU 进程会开启多个线程来完成光栅化,并且优先处理靠近视口区域的块
光栅化的结果,就是一块一块的位图
(8) 画
合成线程拿到每个层、每个块的位图后,生成一个个「指引 (quad)」信息。指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放等变形。变形发生在合成线程,与渲染主线程无关,这就是 transform 效率高的本质原因
合成线程会把 quad 提交给 GPU 进程,由 GPU 进程产生系统调用,提交给 GPU 硬件,完成最终的屏幕成像
6.重绘?重排?
重排/回流 reflow
本质: 重新计算layout树
当进行了会影响布局树的操作后,需要重新计算布局树,会引发 layout。为了避免连续的多次操作导致布局树反复计算,浏览器会合并这些操作,当 JS 代码全部完成后再进行统一计算。所以,改动属性造成的 reflow 是异步完成的
也同样因为如此,当 JS 获取布局属性时,就可能造成无法获取到最新的布局信息。浏览器在反复权衡下,最终决定获取属性立即 reflow。
重绘 repaint
本质: 重新根据分层信息计算了绘制指令
当改动了可见样式后,就需要重新计算,会引发 repaint。由于元素的布局信息也属于可见样式,所以 reflow 一定会引起 repaint。
7.sessionStorage 的跨标签通信
sessionStorage 可以进行跨标签通信,但存在一定的局限
首先,sessionStorage 是会话级别的存储
如何定义一个会话
关键在于两个标签是否存在联系,比如
在 A 页面点击超链接或者在控制台通过 window.open 打开 B 页面,都属于当前页面的延续,因此属于一个会话
在 A 页面已经打开的前提下,然后在 tab 打开同域 C 页面,此时 A 和 C 无直接关系,因此不属于一个会话
因此,session 并不是共享的,而是复制的
总结来说,B 页面打开的时候复制了 A 页面的 session(可以理解为深拷贝),因此二者相互独立,互不影响
8. 单点登录
实际上就是业务的抽离,产品线很多的情况下,可以把用户系统抽离出来,形成一个认证中心。
如此一来,对用户信息的所有操作,都在此认证中心完成
技术实现一般为两种:Session + Cookie 模式 && Token 模式 && Token + RefreshToken 模式
Session + Cookie

① 登录,向认证中心验证
② 验证成功后,认证中心往 Session 表格中记录(key:sid,value:info)
③ 以 Cookie 形式返回sid,即保存在用户浏览器的 Cookie 中
④ 访问子系统时,把此 sid 一同带给子系统
⑤ 子系统把 sid 发给认证中心,认证中心在 Session 表中查询是否存在相应数据
⑥ 存在结果则校验通过,反之失败
分析
2)容错较低,如果认证中心挂掉,所有子系统都会受影响
Token

① 登录,向认证中心验证
② 认证成功后,返回 Token
③ 用户存入 Token,并带着 Token 访问子系统
④ 子系统通过特定的方式(秘钥、特定的加解密算法)验证 Token
分析
↓ 优化
双 Token

① 登录,向认证中心验证
② 认证成功后,返回 Token 和 RefreshToken
(Token 为访问子系统时所携带的,即所有子系统都认识,过期时间较短;RefreshToken 只有认证中心认识,过期时间较长)
③ 用户存入 Token,并带着 Token 访问子系统
④ 子系统通过特定的方式(秘钥、特定的加解密算法)验证 Token
⑤ 若 Token 失效,则子系统提醒用户,用户则带着 RefreshToken 再次请求认证中心,返回新的 Token,然后执行步骤 ③
分析
结合了 Session + Cookie 模式 和 普通 Token 模式,既能实现认证中心对用户的控制,也有效控制了成本
算是一种折中的方案