-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
浅谈浏览器架构、单线程js、事件循环、消息队列、宏任务和微任务 #228
Comments
浅谈Chrome架构浏览器可以是哪种架构?浏览器本质上也是一个软件,它运行于操作系统之上,一般来说会在特定的一个端口开启一个进程去运行这个软件,开启进程之后,计算机为这个进程分配CPU资源、运行时内存,磁盘空间以及网络资源等等,通常会为其指定一个PID来代表它。 先来看看我的机器上运行的微信和Chrome的进程详情:
如果自己设计一个浏览器,浏览器可以是那种架构呢?
如果浏览器单进程架构的话,需要在一个进程内做到网络、调度、UI、存储、GPU、设备、渲染、插件等等任务,通常来说可以为每个任务开启一个线程,形成单进程多线程的浏览器架构。 但是由于这些功能的日益复杂,例如将网络,存储,UI放在一个线程中的话,执行效率和性能越来越地下,不能再向下拆分出类似“线程”的子空间。 因此,为了逐渐强化浏览器的功能,于是产生了多进程架构的浏览器,可以将网络、调度、UI、存储、GPU、设备、渲染、插件等等任务分配给多个单独的进程,在每一个单独的进程内,又可以拆分出多个子线程,极大程度地强化了浏览器。 如何理解Chrome的多进程架构?Chrome作为浏览器届里的一哥,他也是多进程IPC架构的。 Chrome多进程架构主要包括以下4个进程:
Chrome 多进程架构的优缺点
前端最核心的渲染(Renderer)进程包含哪些线程?渲染进程主要包括4个线程:
渲染进程的主线程知识点:
渲染进程的主线程细节可以查阅Chrome官方的博客:Inside look at modern web browser (part 3)和Rendering Performance 渲染进程的合成线程知识点:
下面来看下主线程、合成线程和光栅线程一起作用的过程 图片引自Chrome官方博客:Inside look at modern web browser (part 3) |
浅谈单线程jsjs引擎图
什么是单线程js?如果仔细阅读过第一部分“谈谈浏览器架构”的话,这个答案其实已经非常显而易见了。 那么Chrome中JavaScript运行的位置在哪里呢? 渲染进程(Renderer Process)中的主线程(Main Thread) 单线程js属于浏览器的哪个进程?单线程的js -> 主线程(Main Thread)-> 渲染进程(Renderer Process) js为什么要设计成单线程的?其实更为严谨的表述是:“浏览器中的js执行和UI渲染是在一个线程中顺序发生的。” 这是因为在渲染进程的主线程在解析HTML生成DOM树的过程中,如果此时执行JS,主线程会主动暂停解析HTML,先去执行JS,等JS解析完成后,再继续解析HTML。 那么为什么要“主线程会主动暂停解析HTML,先去执行JS,再继续解析HTML呢”? 这是主线程在解析HTML生成DOM树的过程中会执行style,layout,render以及composite的操作,而JS可以操作DOM,CSSOM,会影响到主线程在解析HTML的最终渲染结果,最终页面的渲染结果将变得不可预见。 如果主线程一边解析HTML进行渲染,JS同时在操作DOM或者CSSOM,结果会分为以下情况:
考虑到最终页面的渲染效果的一致性,所以js在浏览器中的实现,被设计成为了JS执行阻塞UI渲染型。 |
事件循环什么是事件循环?事件循环英文名叫做Event Loop,是一个在前端届老生常谈的话题。 事件循环可以拆为“事件”+“循环”。 如果你有一定的前端开发经验,对于下面的“事件”一定不陌生:
有事件,就有事件处理器:在事件处理器中,我们会应对这个事件做一些特殊操作。 那么浏览器怎么知道有事件发生了呢?怎么知道用户对某个button做了一次click呢? 如果我们的主线程只是静态的,没有循环的话,可以用js伪代码将其表述为: function mainThread() {
console.log("Hello World!");
console.log("Hello JavaScript!");
}
mainThread(); 执行完一次mainThread()之后,这段代码就无效了,mainThread并不是一种激活状态,对于I/O事件是没有办法捕获到的。 因此对事件加入了“循环”,将渲染进程的主线程变为激活状态,可以用js伪代码表述如下: // click event
function clickTrigger() {
return "我点击按钮了"
}
// 可以是while循环
function mainThread(){
while(true){
if(clickTrigger()) { console.log(“通知click事件监听器”) }
clickTrigger = null;
}
}
mainThread(); 也可以是for循环 for(;;){
if(clickTrigger()) { console.log(“通知click事件监听器”) }
clickTrigger = null;
} 在事件监听器中做出响应: button.addEventListener('click', ()=>{
console.log("多亏了事件循环,我(浏览器)才能知道用户做了什么操作");
}) 什么是消息队列?消息队列可以拆为“消息”+“队列”。 队列数据结构图入队出队图在js中,如何发现出队列FIFO的特性?下面这个结构大家都熟悉,瞬间体现出队列FIFO的特性。 // 定义一个队列
let queue = [1,2,3];
// 入队
queue.push(4); // queue[1,2,3,4]
// 出队
queue.shift(); // 1 queue [2,3,4] 假设用户做出了"click button1","click button3","click button 2"的操作。 const taskQueue = ["click button1","click button3","click button 2"];
while(taskQueue.length>0){
taskQueue.shift(); // 任务依次出队
} 任务依次出队: 此时由于mainThread有事件循环,它会被浏览器渲染进程的主线程事件循环系统捕获,并在对应的事件处理器做出响应。 button1.addEventListener('click', ()=>{
console.log("click button1");
})
button2.addEventListener('click', ()=>{
console.log("click button 2");
})
button3.addEventListener('click', ()=>{
console.log("click button3")
}) 依次打印:"click button1","click button3","click button 2"。 因此,可以将消息队列理解为连接用户I/O操作和浏览器事件循环系统的任务队列。 如何实现一个 EventEmitter(支持 on,once,off,emit)?/**
* 说明:简单实现一个事件订阅机制,具有监听on和触发emit方法
* 示例:
* on(event, func){ ... }
* emit(event, ...args){ ... }
* once(event, func){ ... }
* off(event, func){ ... }
* const event = new EventEmitter();
* event.on('someEvent', (...args) => {
* console.log('some_event triggered', ...args);
* });
* event.emit('someEvent', 'abc', '123');
* event.once('someEvent', (...args) => {
* console.log('some_event triggered', ...args);
* });
* event.off('someEvent', callbackPointer); // callbackPointer为回调指针,不能是匿名函数
*/
class EventEmitter {
constructor() {
this.listeners = [];
}
on(event, func) {
const callback = () => (listener) => listener.name === event;
const idx = this.listeners.findIndex(callback);
if (idx === -1) {
this.listeners.push({
name: event,
callbacks: [func],
});
} else {
this.listeners[idx].callbacks.push(func);
}
}
emit(event, ...args) {
if (this.listeners.length === 0) return;
const callback = () => (listener) => listener.name === event;
const idx = this.listeners.findIndex(callback);
this.listeners[idx].callbacks.forEach((cb) => {
cb(...args);
});
}
once(event, func) {
const callback = () => (listener) => listener.name === event;
let idx = this.listeners.findIndex(callback);
if (idx === -1) {
this.listeners.push({
name: event,
callbacks: [func],
});
}
}
off(event, func) {
if (this.listeners.length === 0) return;
const callback = () => (listener) => listener.name === event;
let idx = this.listeners.findIndex(callback);
if (idx !== -1) {
let callbacks = this.listeners[idx].callbacks;
for (let i = 0; i < callbacks.length; i++) {
if (callbacks[i] === func) {
callbacks.splice(i, 1);
break;
}
}
}
}
}
// let event = new EventEmitter();
// let onceCallback = (...args) => {
// console.log("once_event triggered", ...args);
// };
// let onceCallback1 = (...args) => {
// console.log("once_event 1 triggered", ...args);
// };
// // once仅监听一次
// event.once("onceEvent", onceCallback);
// event.once("onceEvent", onceCallback1);
// event.emit("onceEvent", "abc", "123");
// // off销毁指定回调
// let onCallback = (...args) => {
// console.log("on_event triggered", ...args);
// };
// let onCallback1 = (...args) => {
// console.log("on_event 1 triggered", ...args);
// };
// event.on("onEvent", onCallback);
// event.on("onEvent", onCallback1);
// event.emit("onEvent", "abc", "123");
// event.off("onEvent", onCallback);
// event.emit("onEvent", "abc", "123"); |
宏任务和微任务
哪些属于宏任务?
哪些属于微任务?
事件循环,消息队列与宏任务、微任务之间的关系是什么?
事件循环会不断地处理消息队列出队的任务,而宏任务指的就是入队到消息队列中的任务,每个宏任务都有一个微任务队列,宏任务在执行过程中,如果此时产生微任务,那么会将产生的微任务入队到当前的微任务队列中,在当前宏任务的主要任务完成后,会依次出队并执行微任务队列中的任务,直到当前微任务队列为空才会进行下一个宏任务。 微任务添加和执行流程示意图假设在执行解析HTML这个宏任务的过程中,产生了Promise和MutationObserver这两个微任务。 // parse HTML···
Promise.resolve();
removeChild(); 微任务队列会如何表现呢? 图片引自:极客时间的《浏览器工作原理与实践》 过程可以拆为以下几步:
|
浏览器页面循环系统原理图以下所有图均来自极客时间《《浏览器工作原理与实践》- 浏览器中的页面循环系统》,可以帮助理解消息队列,事件循环,宏任务和微任务。
消息队列和事件循环线程的一次执行
setTimeout长任务导致定时器被延后执行 XMLHttpRequest消息循环系统调用栈记录 宏任务 |
关键词:
多进程、单线程、事件循环、消息队列、宏任务、微任务
看到这些词仿佛比较让人摸不着头脑,其实在我们的日常开发中,早就和他们打过交道了。
我来举几个常见的例子:
其实上面举的这些
click, setTimeout, setInterval, Promise,async/await, EventEmitter, MutationObserver, Event类, CustomEvent
与多进程、单线程、事件循环、消息队列、宏任务、微任务
或多或少的都有所联系。而且也与浏览器的运行原理有一些关系,作为每天在浏览器里辛勤耕耘的前端工程师们,浏览器的运行原理(多进程、单线程、事件循环、消息队列、宏任务、微任务)可以说是必须要掌握的内容了,不仅对面试有用,对手上负责的开发工作也有很大的帮助。
The text was updated successfully, but these errors were encountered: