JavaScript之Event Loop

JavaScript基于单线程执行模型,在同一时间只能执行一个任务。那么,为什么JavaScript是单线程?

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

那么JavaScript是如何通过单线程的机制来实现整个运行机制的呢?


JavaScript之Event Loop_第1张图片

这是整个Event Loop的结构图,在JS部分,heap(堆)处理内存分配,而所有同步任务都在主线程上执行,形成一个stack(执行栈),stack里面存放着当前函数执行的调用链。


JavaScript之Event Loop_第2张图片

function foo() {
  console.log(1);

  window.setTimeout(() => { console.log(2); }, 0); console.log(3); } foo();

输出结果自然是1,3,2。再看下一个栗子。

console.log(1);

window.setTimeout(function() {
  console.log(2);
}, 0);

new Promise((resolve) => { window.setTimeout(function() { console.log(3); }, 0); console.log(4); resolve(); }) .then(() => { window.setTimeout(function() { console.log(5); }, 0); console.log(6); }); console.log(7);

要解释这个问题,需要引入另外一个概念:macrotask & microtask:

Macrotasks(宏任务)包含生成dom对象、解析HTML、执行主线程js代码、更改当前URL还有其他的一些事件如页面加载、输入、网络事件和定时器事件。从浏览器的角度来看,macrotask代表一些离散的独立的工作。当执行完一个task后,浏览器可以继续其他的工作如页面重渲染和垃圾回收。

Microtasks(微任务)则是完成一些更新应用程序状态的较小任务,如处理promise的回调和DOM的修改,这些任务在浏览器重渲染前执行。Microtask应该以异步的方式尽快执行,其开销比执行一个新的macrotask要小。Microtasks使得我们可以在UI重渲染之前执行某些任务,从而避免了不必要的UI渲染,这些渲染可能导致显示的应用程序状态不一致。

如下图所示,在一次event loop中,主线程在任务队列中先检查macrotask队列,有的话执行且只执行一个,接着检查microtask队列,有的话执行所有的microtasks,然后再检查是否需要更新渲染。值得注意的是,这一次event loop的执行需要在16ms(1秒钟均匀60帧的画面才能让人感觉流畅)内完成,否则会导致页面卡帧甚至假死。


JavaScript之Event Loop_第3张图片

所以回到上个栗子中,正确的顺序是1,4,7,6,2,3,5。


JavaScript之Event Loop_第4张图片

细心的盆友可能已经发现了,图中不是说会先执行一个macrotask任务,然后再清空microtask队列嘛?为何实际运行时所有的microtasks都在macrotask前面运行了?
这是因为整个代码块(main)开始执行时,所有这些代码在macrotask queue中,主线程将其取出来执行。所以其实是代码块(main)生成的microtasks先执行


JavaScript之Event Loop_第5张图片

是的,没错,task生成的task也算在当次事件循环中,所以如果你像下面这样,microtask生成了microtask的话:

function foo() {
  console.log("Let's get started");
  bar();
  setTimeout(function() {
    console.log("Where is timeout callback?");
  }, 0);
  console.log("foo done");
}

function bar() {
  return Promise.resolve().then(function() {
    console.log("Promise then");
    return bar();
  });
}

foo();

那么恭喜你离成为一名优秀的高级bug工程师又近了一步。

你可能感兴趣的:(JavaScript)