JS的执行原理,事件循环
要了解事件循环,首先我们先了解进程和线程的概念。
- 进程是操作系统运行中的程序。
- 线程是操作系统能够调度的最小单位。
- 一个进程可以包含多个线程,这些线程共享进程的内存和其他资源。
- 每个进程中都会启动一个线程来执行代码,这个线程成为主线程
比如工厂是一个操作系统,车间相当于进程,车间里的工人相当于线程。进程相当于线程的容器。
目前的浏览器一般都是多进程的,一般开启一个tab页就会启动一个进程,防止一个页面的卡死造成所有页面无法响应。
js代码是在一个单独的线程中执行的,单线程统一时间只能干一件事,如果一段代码非常耗时,那么该线程会被阻塞。
在浏览器的事件循环中,主线程负责执行 同步任务,而异步任务是由任务队列处理。这种机制保证了异步任务可以在适当时机插入执行,从而实现了JavaScript的非阻塞异步执行。
事件循环流程如下:
- 主线程读取JavaScript代码,形成相应的堆和栈。
- 当主线程遇到异步任务时,将其委托给异步进程处理。
- 异步任务执行完成后,将响应的回调函数推入任务队列。
- 主线程执行完同步任务后,检查任务队列,如果有任务,则按照先进先出的原则将任务推入主线程执行。
- 重复执行上述步骤,知道任务队列为空。
同步任务
同步任务按照代码书写顺序一步一步执行的。当主线程执行同步任务时,会阻塞后续代码的执行,直到当前任务执行完成。包括函数、变量定义、算数运算、输出。如:
const a = 1;
console.log(a);
function fn() {}
异步任务
异步任务是在主线程执行的同时,通过回调函数或其他机制委托给其他线程或事件来处理的任务。在执行异步任务时,主线程不会等待任务完成,而是继续执行后续代码。异步任务包括:
- 回调函数
callback Promise``async/awaitGenerator- 事件监听
- 发布/订阅
- 计时器
requestAnimationFrameMutationObserverprocess.nextTick- I/O操作
async function fn() {
await request1();
await request2();
}
setTimeout(() => {
console.log(123)
}, 123)
任务队列
浏览器的事件循环维护两个队列:宏任务队列和微任务队列。JavaScript引擎遵循事件循环机制。在执行完当前的宏任务后,会检查微任务队列,执行其中的微任务,然后再取下一个宏任务执行。
1、 宏任务是一些较大粒度的任务,包括:
- 所有同步任务
- I/O操作
setTimeout``setIntervalrequestAnimationFrame- 事件监听、回调函数等
2、 微任务是一些粒度较小的任务,包括:
Promise的then、catch、finallyasync/awaitGeneratorMutationObserverprocess.nextTick
任务执行过程
- 同步任务在主线程排队执行,异步任务在事件队列排队进入主线下执行。
- 遇到宏任务则推进宏任务队列,遇到微任务则推进微任务队列。
- 执行宏任务,执行完毕后检查当前层的微任务并执行。
- 继续执行下一个宏任务,执行对应层次的微任务,直至所有任务执行完。
实例
const promise1 = new Promise((resolve, reject) => {
console.log('promise1')
})
promise1.then(() => {
console.log(3);
});
console.log('1', promise1);
const fn = () => (new Promise((resolve, reject) => {
console.log(2);
resolve('success')
}))
fn().then(res => {
console.log(res)
})
console.log('start')