JS 事件循环机制
JS 单线程
进程:
系统进行资源分配和调度的一个独立单位,可简单理解为主任务
线程:
安排 CPU 执行的最小单位,可理解为子任务
进程与线程:
一个进程可以有多个线程并发执行
线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程
那么 JS 单线程是如何实现异步的呢:
就是通过事件循环(event loop)实现异步
任务队列
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
console.log(3)
// 1 3 2
上面的问题里,setTimout内匿名函数没有立即执行,而是添加到任务队列,等满足一定条件取执行
- 异步任务操作会将相关回调添加到任务队列
- 而不同的异步操作添加到任务队列的时机也不同,如
onclick、setTimeout、ajax,这些异步操作是由浏览器内核执行
事件循环
- 执行一个宏任务后,其中同步任务立即执行、异步任务放入事件队列
- 执行完取微任务队列执行完毕
- 再次检查有无宏任务,重复执行以上步骤
- 若无宏任务,执行下一事件循环
即,主线程不断从消息队列获取消息、执行消息,这个过程成为事件循环
同步任务和异步任务
- JS 里的一种任务分类方式就是将任务分为:同步任务和异步任务
- 同步就是后一个任务等待前一个任务执行完毕后,再执行,执行顺序和任务的排列顺序一致
- 对于耗时的操作或时间不确定的操作,使用异步成了必然的选择
宏任务与微任务
将任务进行更精确的定义:任务队列的任务又分为宏任务和微任务
- 执行一个宏任务后,执行完取微任务队列执行完毕
- 再次检查有无宏任务,重复执行
- 若无宏任务,执行下一事件循环
-
常见宏任务:
- script
- 定时器 setTimeout
- 事件绑定
- Ajax
- 回调函数
- Node 中 fs 可进行异步的 I/O 操作
- UI 交互事件
-
常见微任务:
- Promise.then
- async、await
- process.nextTick
- MutationObserver(HTML5 新特性,监控某个节点)
例子
-
Promise、Promise.then
let p = new Promise(resolve => { console.log("promise 1") resolve(1) }).then(() => { console.log("then") }) console.log("1") // promise1 // 1 // then.then为异步、微任务,待同步代码执行后执行Promise
若未
resolve()后面的then不会执行
输出结果:4 1 3 5 6 2
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);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log('script end');
执行顺序:
script start async1 start async2 promise1 script end async1 end promise2 settimeout