事件循环
浏览器的 js 是单线程的,也就是说同一时刻,最多也只有一个代码在执行,但是浏览器又可以很好的处理异步请求。这是为什么呢?
关于执行中的线程:
主线程:也就是 js 引擎执行的线程,这个线程只有一个,页面渲染,函数处理都在这个主线程上执行。
工作线程:也称幕后线程,这个线程可能存在于浏览器或 js 引擎内,与主线程是分开的,处理文件读取,网络请求等异步事件。
在执行代码过程中,如果遇到一些异步代码(比如setTimeout,ajax,promise.then以及用户点击等操作),那么浏览器就会将这些代码放到另一个线程(在这里我们叫做幕后线程)中去执行,在前端由浏览器底层执行,在 node 端由 libuv 执行,这个线程的执行不阻塞主线程的执行,主线程继续执行栈中剩余的代码。
当幕后线程(background thread)里的代码执行完成后(比如setTimeout时间到了,ajax请求得到响应),该线程就会将它的回调函数放到任务队列(又称作事件队列、消息队列)中等待执行。而当主线程执行完栈中的所有代码后,它就会检查任务队列是否有任务要执行,如果有任务要执行的话,那么就将该任务放到执行栈中执行。如果当前任务队列为空的话,它就会一直循环等待任务到来。因此,这叫做事件循环。
任务队列
那么,问题来了。如果任务队列中,有很多个任务的话,那么要先执行哪一个任务呢?其实(正如上图所示),js是有两个任务队列的,一个叫做 Macrotask Queue(Task Queue) 大任务, 一个叫做 Microtask Queue 小任务
Macrotask 常见的任务:
- setTimeout
- setInterval
- setImmediate
- I/O
- 用户交互操作,UI渲染
Microtask 常见的任务:
- Promise(重点)
- process.nextTick(nodejs)
- Object.observe(不推荐使用)
事件循环执行流程如下:
主线程执行同步代码,遇到异步的放入 enevt Table 中,执行指定操作,完成后进入 enevt queue 中,当同步的代码执行完毕,则执行event queue 中代码
console.log(1)
setTimeout(function() {
//settimeout1
console.log(2)
}, 0);
const intervalId = setInterval(function() {
//setinterval1
console.log(3)
}, 0)
setTimeout(function() {
//settimeout2
console.log(10)
new Promise(function(resolve) {
//promise1
console.log(11)
resolve()
})
.then(function() {
console.log(12)
})
.then(function() {
console.log(13)
clearInterval(intervalId)
})
}, 0);
//promise2
Promise.resolve()
.then(function() {
console.log(7)
})
.then(function() {
console.log(8)
})
console.log(9)
结果如下
1
9
7
8
2
3
10
11
12
13
async/await 又是如何处理的
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
输出结果为
/**
* async1 start
* async2
* promise1
* script end
* async1 end
* promise2
* */
js 的异步事件,因为有事件循环机制,异步事件就是由事件驱动异步非阻塞的,上面的栗子已经很好证明了。所以 nodejs 适合处理大并发,因为有事件循环和任务队列机制,异步操作都由工作进程处理(libuv),js 主线程可以继续处理新的请求。缺点也很明显,因为是单线程,所以对计算密集型的就会比较吃力,不过可以通过集群的模式解决这个问题。