⚠️本篇只讨论浏览器部分
概念
进程(process)和线程(thread)
进程(process):计算机已经运行的程序,是操作系统管理程序的一种方式。 线程(thread):操作系统能够运行运算调度的最小单位,通常情况下它被包含在进程中。
浏览器是多进程的,通常每个tab页都是一个进程,一个页面崩溃不会影响其他页面
- 进程是操作系统分配资源的最小单位,线程是程序执行的最小单位。
- 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
- 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号)。
- 调度和切换:线程上下文切换比进程上下文切换要快得多。
以谷歌浏览器为例,由以下线程组成
-
GUI 渲染线程
-
JavaScript引擎线程
-
定时触发器线程
-
事件触发线程
-
异步http请求线程
GUI渲染线程与JavaScript引擎互斥、所以JS会阻塞页面的加载
调用栈
管理执行上下文的栈为调用栈、先进后出,当入栈的执行上下文超过了一定数目,JS引擎就会报栈溢出错误
微任务和宏任务
JavaScript执行分为同步任务和异步任务,同步任务压入执行栈中执行,异步任务放入任务队列中,任务队列又分为微任务队列和宏任务队列
宏任务有哪些
- script标签中运行代码
- 事件的回调函数
setTimeout
,setInterval
,setImmediate
,I/O
,UI rendering
,requestAnimationFrame
微任务有哪些
Promise
MutationObserver
process.nextTick
,Promises
,Object.observe
,MutationObserver
,fetch
任务队列
先进先出的数据结构
执行机制
简单概括
- 执行栈中的同步任务,如果存在微任务,则该任务进入微任务队列中;如果存在宏任务,则该任务进入宏任务队列中
- 在执行栈清空后,检查微任务队列中的微任务,微任务队列里的微任务依次出列执行(先进先出),直至队列清空;
- 检查宏任务队列中的宏任务,宏任务队列取出一个任务执行,执行步骤1
⚠️在所有任务开始的时候,由于宏任务中包括了script,所以浏览器会先执行一个宏任务,在这个过程中你看到的延迟任务(例如setTimeout
)将被放到下一轮宏任务中来执行。
浏览器渲染事件循环
- 一开始整个脚本作为一个宏任务执行
- 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
- 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
- 执行浏览器UI线程的渲染工作
- 检查是否有Web Worker任务,有则执行
- 执行完本轮的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空
什么是Event Loop
-
JavaScript是单线程的语言
-
事件循环是JavaScript的执行机制
-
JavaScript事件分同步和异步事件
JavaScript引擎在等待任务,执行任务中不断循环,宏任务和微任务执行完成后都会判断是否还有微任务,有的话执行微任务,没有就执行宏任务,如此循环
用代码表示即
for (const macroTask of macroTaskQueue) {
handleMacroTask();
for (const microTask of microTaskQueue) {
handleMicroTask(microTask);
}
}
题目1
jsconsole.log(0) setTimeout(_ => console.log(4),0) new Promise(resolve => { resolve() console.log(1) }).then(_ => { console.log(3) }) console.log(2)
题目2
jsasync function fn1() { console.log(1) } async function fn2() { console.log(2) } async function result() { await fn1() fn2() setTimeout(() => { console.log(3); }); Promise.resolve().then(res => console.log(4)) console.log(5); } result() console.log(6) Promise.resolve().then(res => console.log(7)) setTimeout(() => { console.log(8); });
node
Node
的事件循环是基于libuv
实现的
待更新
总结
- js工作机制是单线程的,但是任务有同步任务和异步任务之分
- 执行任务的地方叫执行栈,而异步任务会进入任务队列等待被执行
- 执行栈中的任务执行完毕之后,执行栈会去查看任务队列中还有没有未执行的任务。有,则拿出来执行;没有,则开始下一次的工作
- 重复第三点的工作,不断地处理后续的任务,这样的工作机制就是事件循环
F&Q
- 为啥是单进程
如果 JavaScript 引擎线程不是单线程的,那么可以同时执行多段 JavaScript,如果这多段 JavaScript 都修改 DOM,那么就会出现 DOM 冲突。所以避免 DOM 渲染的冲突
- 为什么有异步
引入单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。这同时又导致了一个问题:如果前一个任务耗时很长,后一个任务就不得不一直等着。