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