js事件循环机制
1. 什么是事件循环?
js引擎并不是独立运行的,它
运行在宿主环境
中,对多数开发者来说通常就是web浏览器。经过最近几年的发展,js已经超出了浏览器的范围,进入了其他环境。
所以这些环境都有一个共同“点”(thread,也指线程
),即它们都提供了一种机制来处理程序中多个块的执行,且执行每块时调用js引擎
,这种机制被称为事件循环
。
2. event loop它最主要是分三部分:主线程、宏队列(macrotask)、微队列(microtask)
js的任务队列分为同步任务和异步任务,所有的同步任务都是在主线程里执行的,异步任务可能会在macrotask或者microtask里面。
3. 主线程
就是访问到的script标签里面包含的内容,或者是直接访问某一个js文件的时候,里面的可以在当前作用域直接执行的所有内容(执行的方法、new出来的对象)
4. js引擎的两大特点:单线程和非阻塞
- 单线程:只有一个主线程来处理任务。
- 非阻塞:当执行异步任务时,不必等到结果返回,主线程会挂起(pending)这个任务,然后根据一定规则执行回调函数(事件循环机制)。
JS通常是非阻塞的,除了某些特殊情况,JS会停止代码执行:
alert, confirm, prompt(除了Opera)。
“页面上的程序正忙”的系统对话框弹出。
5. 任务分类:同步和异步
- 同步任务(macrotask ):会立即执行的任务。
- 异步任务(microtask ):不会立即执行的任务,可细分为一下两种:宏任务和微任务。
--宏任务:script, setTimeout, setInterval, setImmeditate, T/O, UI rendering
-- 微任务:process, nextTick, promise.then(), object.observe, MutationObserver
6. 执行顺序
1、先执行主线程
2、遇到宏队列(macrotask)放到宏队列(macrotask)
3、遇到微队列(microtask)放到微队列(microtask)
4、主线程执行完毕
5、执行微队列(microtask),微队列(microtask)执行完毕
6、执行一次宏队列(macrotask)中的一个任务,执行完毕
7、执行微队列(microtask),执行完毕
8、依次循环。。。
7. 执行栈和任务队列
占内存和堆内存:
栈内存保存着JS的变量和指向堆内存中对象的指针,堆内存保存着对象。
- 执行栈:
后进先出的数据结构,当函数被调用时添加到执行栈顶部,从栈顶移出。当一个任务为同步任务时,则会被立即执行,执行完移出;如果是异步任务,则会交给异步处理模块处理,当异步任务回调达到触发条件时,会将回调函数添加到任务队列中,如果是宏任务,则添加到宏任务队列中,如果是为微任务,则添加到微任务队列中。
- 任务队列:
图示: image.png任务队列读取任务的顺序为宏任务-微任务(微任务队列全部执行结束才进入到下一轮宏任务)-宏任务-微任务的顺序;当执行栈中的任务全部完成之后会从事件队列中读取一个任务添加到执行栈。一个任务队列中可以有多个宏任务队列,但只能有一个微任务队列。
- 引入微任务队列后,事实上事件循环执行的流程是这样的:
- 一开始把一整段的JS脚本作为第一个宏任务执行
- 在执行过程中,同步代码则直接运行,过程中存在宏任务则进入到宏任务队列,微任务在微任务队列中入列。
- 在当前宏任务执行完成后,检查微任务队列,若存在微任务则按序全部执行完毕。
- 继续检查宏任务队列,执行下一个宏任务,如此反复。
理解了原理后,可以根据这个步骤来多看几个例子,尝试写出打印的结果。
Promise.resolve().then(()=>{
console.log('Promise1')
setTimeout(()=>{
console.log('setTimeout2')
},0)
});
setTimeout(()=>{
console.log('setTimeout1')
Promise.resolve().then(()=>{
console.log('Promise2')
})
},0);
console.log('start');
// start
// Promise1
// setTimeout1
// Promise2
// setTimeout2
- 有个小 tip:从规范来看,microtask (微任务)优先于 macrotask(宏任务) 执行,所以如果有需要优先执行的逻辑,放入microtask 队列会比 task 更早的被执行。