浏览器内核、浏览器中的线程进程与JS单线程
总览
浏览器内核、浏览器中的进程与线程、JS单线程、事件循环相关笔记
# 浏览器内核
浏览器作为JavaScript的宿主环境,主要分成两部分:
- 渲染引擎:负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入CSS等),以及计算网页的显示方式,然后渲染到用户的屏幕上。
- JS引擎:解析和执行JavaScript来实现逻辑和控制DOM进行交互。目前JS引擎越来越独立,内核就倾向于只指渲染引擎。
# 浏览器中的进程
浏览器开多个进程的优势包括如果只有一个进程那么如果有一处地方崩溃整个浏览器都会停止工作。
浏览器进程:浏览器的主进程,负责控制浏览器除标签页外的界面,包括地址栏、书签、前进后退按钮等,以及负责与其他进程的协调工作,同时提供存储功能
网络进程:负责发起和接受网络请求,以前是作为模块运行在浏览器进程一时在面的,后面才独立出来,成为一个单独的进程
渲染进程:负责控制显示tab标签页内的所有内容,核心任务是将HTML、CSS、JS转为用户可以与之交互的网页,排版引擎Blink和JS引擎V8都是运行在该进程中,默认情况下Chrome会为每个Tab标签页创建一个渲染进程,但是,有时候浏览器会将多个进程合并,如打开了多个空白标签页,(Chrome浏览器未来会变成一个站点一个进程即根据站点创建进程而不是根据标签页),出于安全考虑,渲染进程都是运行在沙箱模式下
GPU进程:负责整个浏览器界面的渲染。Chrome刚开始发布的时候是没有GPU进程的,而使用GPU的初衷是为了实现3D CSS效果,只是后面网页、Chrome的UI界面都用GPU来绘制,这使GPU成为浏览器普遍的需求,最后Chrome在多进程架构上也引入了GPU进程
插件进程:主要是负责插件的运行,因为插件可能崩溃,所以需要通过插件进程来隔离,以保证插件崩溃也不会对浏览器和页面造成影响,每种类型的插件对应一个进程,仅当使用该插件时才创建。
网络进程。主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
# 渲染进程中的线程
渲染进程在沙盒中(沙盒是一种安全措施,它可以限制一个进程所能访问的资源和执行的操作,保护整个操作系统的安全)保证安全性,用户在浏览网页即使受到攻击不会影响操作系统
- GUI渲染线程:负责渲染页面,解析html和CSS、构建DOM树、CSSOM树、渲染树、和绘制页面,重绘重排也是在该线程执行
GUI渲染引擎和JS执行引擎互斥,当GUI渲染引擎解析html时,发现了script标签时,会立即挂起解析html的任务,然后开始解析js代码,直到所有js代码执行完成后,继续执行html解析渲染
- JS引擎线程:一个tab页中只有一个JS引擎线程(单线程),负责解析和执行JS。它GUI渲染进程不能同时执行,只能一个一个来,即互斥,如果JS执行过长就会导致阻塞掉帧
- 计时器线程:指setInterval和setTimeout,因为JS引擎是单线程的,所以如果处于阻塞状态,那么计时器就会不准了,所以需要单独的线程来负责计时器工作
- 异步http请求线程: XMLHttpRequest连接后浏览器开的一个线程,比如请求有回调函数,异步线程就会将回调函数加入事件队列,等待JS引擎空闲执行
- 事件触发线程:主要用来控制事件循环,比如JS执行遇到计时器,AJAX异步请求等,就会将对应任务添加到事件触发线程中,在对应事件符合触发条件触发时,就把事件添加到待处理队列的队尾,等JS引擎处理,会开启事件循环机制,该机制会不断得访问事件队列的头部
# JS单线程
JS执行引擎是单线程的,这和JS的用途有关,JS作为浏览器的脚本语言,主要用处是与用户进行交互,以及操作DOM的,如果JS是多线程,当在不同线程进行不同的DOM操作,会造成更复杂的一些问题,所以从一开始,JS就是单线程的。
JS单线程就意味着,我的代码必须是从上到下,一行一行执行。一个代码块执行完毕之后,才能进行另一个代码块的执行,这就是同步执行代码
存在什么问题:
那么此时,我们想要从服务端获取某一些数据然后渲染到页面上,如果仍然是同步执行,我们需要等待数据全部加载完成之后,才能执行接下来的代码;如果这个数据的请求特别慢,那么页面就极其不友好。
如何解决:
异步回调是两个词,异步指的是,JS执行引擎会将某些任务挂起,交由他的兄弟线程进行处理,等到兄弟线程处理完成之后,JS执行引擎再回去执行其回调函数,他不会阻塞代码的执行,异步代码执行错误,不会影响其他同步代码的执行。而回调函数指的是,我先定了一个函数,但是目前我不想调用,当在某个事情发生或者到了某个特定的时间,我会去调用这个函数,这个就称为回调函数。
触发异步:
网络请求,如ajax请求(交由异步http线程处理)
定时任务,setTimeout, setInterval(交友定时器线程)
回调函数的应用场景:
- 网络请求成功或者失败的回调函数
- 定时任务在到达指定事件后触发的函数
- 用户界面交互时发出的事件的处理函数
Browser进程请求服务端,服务端返回了html资源
GUI渲染线程对html进行解析
当解析到script标签时,停止GUI渲染线程,并将之后的任务,存在一个队列中(等待执行)
JS执行线程接管,解析并执行js代码,遇到下面三种情况,会请求兄弟线程协助
- 为dom元素,添加事件 => 通过事件触发线程,生成事件监听器(待确认)=> 监听器监听到了用户触发事件的行为之后,将回调函数,推入事件队列中
- 解析到setTimeout或者setInterval定时器代码时 => 通过定时器线程,开启定时任务 => 定时器时间到达之后,会将回调函数推入事件队列
- 遇到ajax请求 => 通过异步http请求线程,发送http请求 => 服务端返回响应后,会将成功或者失败的回调函数推入事件队列
当js执行线程中的代码执行完成之后,即同步执行的代码全部执行完成,(异步代码不会阻塞同步代码执行)。JS执行线程为空时,GUI渲染线程会重新接管,将剩下的等待执行的解析任务完成。
然后事件触发线程会开启事件循环机制,该机制会不断得访问事件队列的头部
如果事件队列的头部不为空,则将其取出,推入到JS执行引擎的执行栈中,并立即开始执行。然后从4-6再次循环(这个循环就叫做Event Loop),直至JS执行线程和事件队列中都为空时结束。
事件触发线程 =>GUI渲染线程 => JS执行线程 => 异步http线程 => 推到事件队列(事件触发线程) => GUI渲染线程(如果仍有等待的任务) => JS执行线程 => 定时器线程
# 参考
javascript - 从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理 - 程序生涯 - SegmentFault 思否 (opens new window)
浏览器的进程与线程--深入同步、异步问题 - 知乎 (zhihu.com) (opens new window)