浏览器的进程与线程
进程是正在运行的程序,它包括代码,操作系统分配的资源,例如内存。 进程是操作系统分配资源的最小单位。 线程是cpu调度的最小单位。我可能一个进程内包含多个线程。线程共享同一进程的资源。拿QQ来举例子,我们的接收消息,就是一个单独的线程,为了防止主界面的阻塞。而对于下载这种需要独立管理资源,即使出错,也不会干扰我们的主程序。使用单独的进程。
我们的浏览器是多进程的。他主要有以下五种进程
- 主进程
- 进行协调管理子进程,例如每打开一个tab他会创建一个渲染进程。
- 管理用户界面,例如地址栏,用户的收藏,前进返回。
- 渲染进程
- 负责html文档的解析,js的执行以及页面的渲染,事件处理。每个tab页代表一个进程。防止不同tab页崩溃影响其他的tab页。
- 但是当浏览器的资源紧急,例如打开多个tab页面,他可能会将同一网址tab页面公用一个渲染进程,甚至不同网址tab共用一个进程。
-
GPU进程
负责3D 绘制 视频解码等。当我们的渲染进程渲染好页面给出指令,最终是由3D进程进行绘制页面。
-
插件进程
负责管理我们浏览器的插件,例如 vue develtools,react devtools。防止插件崩溃影响页面。
-
网络进程
- 负责发起发起请求,将请求发送到服务器。还负责cookie,缓存等网络协议。
- 负责接收处理响应。
- 处理网络的安全
-
实用程序进程
文件的下载,打印,截图这类系统操作。
进程间通信通过IPC来进行通信。例如:
渲染进程中包括以下六个线程
- js引擎线程 负责我们js脚本的执行。他是单线程结构。
- http线程 负责处理ajax请求。当请求完成触发回调,通知事件触发线程。例如将fetch代码到http逻辑格式的转换。
- 定时器线程 负责定时,到时间同时事件触发线程。
- 事件触发线程 负责控制事件,例如鼠标点击等。
- GUI线程 负责我们HTML文档的解析,进行页面的渲染工作。
- webworker js 单线程对cpu密集型任务显得乏力,例如在js脚本你使用for进行一亿次循环,就会堵塞页面。 相当于是浏览器的多线程机制。单独开出来一个线程。我们可以将耗时计算放在这里。但是他不能访问DOM。
事件循环机制
js是单线程的,我们不能让耗时性任务阻塞我们的主线程。我们将耗时性任务放入队列中,等待我们的主线程 完成之后再执行异步任务。可能异步任务又会产生新的异步任务,我们又将其放入任务队列中。如此维护事件执行顺序的机制叫做事件循环机制。
任务队列中的任务分为宏任务和微任务。
宏任务有
- 1.定时器
- 2.IO
- 3.事件
- 4.requestAnimationFrame
- 5.script
- 6.setImmidate(在IO操作的回调中比定时器快)
微任务有
- promise.then函数中的回调
- queenmicroTask()中的回调 //底层实现使用promise.resolve方法
- process.nextTick(); // 达到递归深度会有一个警告机制。
- mutationObserver();//H5新特性,监听DOM变化
注意:
浏览器的事件循环
首先VSync是我们的显示器发出的一个信号,告诉浏览器我们要开始渲染下一帧(显示器显示),可以接受下一帧的画面数据了。 浏览器渲染的目标是提前将渲染好的画面(GPU的纹理)准备好,当显示器发出VSyns信号,我们就可以提供这个画面给他,让显示器渲染。
- 首先,我们的宏任务会去执行
- 执行完宏任务,清空我们的微任务队列
- 这时候会去检查一下我们离下一次的VSync信号是否还有足够的时间,以及是否时间过长(如果时间不够则跳过此次渲染,如果还有过长的时间则不会进行下面的渲染,而是继续事件循环)
- 首先会去执行我们的scroll和resize事件,他们相当于是自带一个raf节流
- 执行RAF
- 进行页面的渲染工作,样式计算,重绘重排...
- 检查离Vsync还有多久?还有时间的话执行我们的RIC。
- 接收到VSync信号,将渲染好的画面提供给显示器,开启新的一帧
看完上述流程,我们该有些疑问了,我们可能到达了一定的时间才会进行一次页面的渲染工作,也就是可能宏任务执行了多轮,浏览器怎么确保我要执行多少宏任务呢?
我们的浏览器会去维护一个期望的渲染时间点,这个时间点的周期跟我们的vsync的周期一致,如果距离这个时间点还很远,我们的浏览器可能会推迟渲染,执行更多的任务。当我们的渲染流程结束,假如还有时间,就会去执行我们的RIC。
发现没,其实我们在瞬间往宏任务队列里塞入100000个短时性的js任务,实际上并不会导致我们的页面的卡死,因为我们的页面的渲染是由浏览器调度决定的,每次循环,浏览器都会查看有没有到我们的渲染时间点,执行渲染。但是可能会造成我们的点击,滑动事件的回调不执行无响应。 长耗时性任务(js执行,渲染)才是我们页面的fps下降的原因。当然,我们的短宏任务操控了页面的话,就会造成大量渲染卡死页面。
如此设计也是为了防止在我们渲染的同时修改元素的属性,造成页面数据的不一致性。也就是我们常常说的js渲染和主线程互斥。当然,有些情况也会进行页面的立即渲染,例如。
- element.style.width=10px; const width = window.getElement.width 修改样式并立即获取
- offestTop 触发重新渲染
- scrolly 获取滚动位置
注意:
-
resize事件和scroll事件在渲染流程中触发,浏览器会自动合并成最后一次,在页面渲染前执行,相当于自带一个raf节流。
-
不是每轮eventLoop都有页面更新。