JS小结之事件循环
JavaScript的单线程意思是JS引擎在执行和解释JS代码的时候,都是在一个线程里完成的,而这个线程也就是所谓的“主线程”,但是实际上在处理其他的一些特殊操作的时候,是会为其开辟新的线程来专门执行,比如:
- 处理Ajax请求
- 处理DOM事件
- 定时器
- 文件的读写
等等,这些也就是我们所说的“异步”操作。
当代码运行到它们,我们会将他们要在这件事情完成后执行的代码注册,到达时间点了,再去触发这些注册函数。
但我们何时才能知道应该选择哪一个任务去做?这就是JavaScript中的事件循环模型所规定的机制。
机制介绍
JavaScript执行引擎的主线程在运行时产生一个堆和一个栈。
程序代码依次进入栈中(先进后出)。
当调用setTimeout()方法,浏览器相应的内核模块开始进行监听发生条件的触发。
如果达到了触发条件,方法就会加入回调任务队列。
引擎栈的代码执行完毕的时候,主线程才会去读取任务队列,依次执行满足触发条件的回调函数。
例子1
console.log('start'); // 入栈,执行出栈
//Timer1 入栈,出栈把回调函数放入timer模块
setTimeout(function(){
console.log('hello');
},200);
//Timer2 同上
setTimeout(function(){
console.log('world');
},100);
console.log('end'); // 入栈,执行出栈
// 执行栈已经被清空,这时候Timer模块检查异步代码
// 如果触发条件达成,回调函数加入任务队列
// Timer2早于Timer1被加入到任务队列中,主线程空闲,于是检查任务队列是否有可以执行的,以此循环检查。
例子2
console.log(1);
//Time1
setTimeout(function(){
console.log(2);
},300);
//Time2
setTimeout(function(){
console.log(3)
},400);
// for循环所需时间长,此时前面两个回调函数都已在任务队列
for (var i = 0;i<10000;i++) {
console.log(4);
}
//Time3
setTimeout(function(){
console.log(5);
},100);
宏队列与微队列
任务队列分为两类,一种是宏队列,一种是微队列。
宏队列在每次事件循环中只会提取执行一个,
微队列会把队列中所有的任务都提取出来执行再进行下一次的提取。
并且微队列中的任务要比宏队列中的任务优先检查执行。
举个例子
// (回调)加入微队列
process.nextTick(() => {
console.log('nextTick')
})
// 加入宏队列
setTimeout(() => {
console.log('setTimeout1')
})
// then回调加入微队列
Promise.resolve()
.then(() => {
console.log('then')
})
// 加入宏队列
setTimeout(() => {
console.log('setTimeout2')
})
// 主线程,先执行
console.log('end')
在输出end后,主栈为空。
就检查微队列是不是为空,有两个已经加入,全部执行。
再看宏队列,虽然有两个,但是它这次只执行一个。
再进行第二轮循环,只有宏队列还剩下一个任务。
所以结果是:
end nextTick then setTimeout1 setTimeout2
P.S:这个部分我原来在Promise相关的小结上也小小总结过,这次联系起来更加深入的理解。
宏队列代表
- setTimeout
- setInterval
- setImmediate
- requestAnimationFrame
- I/O
- UI rendering
微队列代表
- process.nextTick
- Promises
- Object.observe
- MutationObserver