浏览器内核及event loop相关
一、浏览器有哪些进程
- 浏览器主进程:主要负责显示界面、提供前进后退收藏等交互行为,提供子进程管理功能、存储用户数据。
- GUP进程:GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程
- 网络进程:主要负责页面的网络资源加载。
- 插件进程:主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。
- 渲染进程:又称之为浏览器内核。
二、浏览器内核有哪些线程
- GUI线程
负责把html解析成dom tree,把css解析成css rule,然后把两者结合,形成render tree。然后计算出layout tree放入浏览器内存中供浏览器主进程显示到界面上;
当界面需要重绘或回流时,该线程会被执行;
该线程与JS引擎线程是互斥的,当JS引擎在执行时,该线程会处于暂停状态。这也就是为什么一段JS代码执行时间比较久时页面会出现空白或卡顿的原因
- JS引擎线程(单线程执行)
又叫JS内核,负责解析和执行JS代码.为什么JS是单线程执行的?假如JS是多线程执行的,一个线程要添加DOM、另一个线程要删除DOM,就会乱套,这就像两台电脑不能同时使用同一台打印机一样的道理;
JS是单线程执行的、那Worker为何可以达到多线程的效果?
创建WebWorker时,JS引擎线程向浏览器申请了worker专用的线程做为js引擎线程的子线程,两者通过postMessage Api进行线程间的通讯。
ShardWorker是浏览器中所有tab标签共享的,每个tab标签都有独立的render进程,所以SharedWorker不属于某个Render进程,而是浏览器开了独立的进程来管理。
- 事件触发线程
主要负责事件队列的维护,当事件触发时会被事件线程捕获到,事件线程把事件处理程序放入到事件队列中,这一过程不会打断JS引擎线程的执行
- 定时器触发线程
setTimeout与setInterval所在的线程;
由于js引擎是单线程的,如果处于阻塞线程状态,则会影响记时的准确性,因此需要单独的线程来计时并触发事件
当到达指定时间时,setTimeout回调代码也会被加入到事件队列,注意是加入事件队列,而不是立即执行,因为如果在队列中还有其他待执行的代码时就不会执行;
setInterval的情况更特殊,当到达指定时间时,如果上一次的回调函数还在队列中等待执行,则直接跳过而不做任何操作。
- 异步请求线程
XmlHttpRequest所在线程;
假如不采用单独的线程而是直接使用JS引擎线程,从发出请求到等待服务端给出响应的这段时间,JS引擎就会一直处于运行状态,原本可以继续执行后面的代码的,现在只能等待
三、event-loop
js执行是单线程的,为了能让UI渲染、脚本执行、各种事件、网络请求等进行协同工作,将任务分成了同步任务和异步任务来避免阻塞问题,同步任务直接压入调用栈中等待js引擎所在线程执行,异步任务会在有了结果后将注册的回调函数放入到相应的任务队列,等待调用栈为空时去采用轮询机制去执行。
异步任务分为宏任务队列(Macro Task Queue)和微任务队列(Micro Task Queue),不同的异步任务会被放入不同的队列中。
- 宏任务:包括
setTimeout
、setInterval
、I/O操作。 - 微任务:包括
Promise
的回调、process.nextTick
(Node.js)、MutationObserver
。
执行栈(call stack)中的同步任务会被按顺序执行,直到栈为空,然后开始执行异步任务:
- 执行所有微任务,直到微任务队列为空。
- 执行一个宏任务
- 重复以上步骤。
经典面试题:
async function async1(){ console.log("async1 start") await async2() console.log("async1 end") } async function async2(){ console.log("async2") } console.log("script start") setTimeout(function(){ console.log("settimeout") },0) new Promise(function(resolve){ console.log("promise") resolve() }).then(function(){ console.log("promise then") }) async1()
执行顺序:
-
同步代码(栈中的任务):
console.log("script start")
→"script start"
setTimeout
被添加到宏任务队列。new Promise
的同步代码console.log("promise")
→"promise"
.then()
的回调console.log("promise then")
被添加到微任务队列。
-
执行
async1()
:console.log("async1 start")
→"async1 start"
- 调用
async2()
,console.log("async2")
→"async2"
- 执行到
await async2()
时,async1
会暂停,等待async2()
执行完成。
-
执行微任务队列:
- 在执行栈清空后,微任务队列中的任务会被立即执行。
- 微任务队列中的任务顺序:
Promise.then()
回调会首先被执行,console.log("promise then")
→"promise then"
-
继续执行
async1()
:- 执行
console.log("async1 end")
→"async1 end"
- 执行
-
执行宏任务队列:
- 在微任务队列中的所有任务执行完成后,事件循环会去执行宏任务队列中的任务。
setTimeout
的回调会被执行,console.log("settimeout")
→"settimeout"