前端的经典的面试题——Event Loop(事件循环)

我们今天来说说javaScript中的代码执行顺序问题,这是一道非常经典的面试题。

这里我们需要知道的一个知识点是:javascript是一门单线程的脚本语言,代码的执行顺序是自上而下执行的,我们来看一下下面这段代码的执行结果:

  console.log(1);

  console.log(2);

  console.log(3);

  //执行结果:1;2;3;

这段代码是自上而下执行的。

我们再看下面这段代码的执行结果:

  console.log(1);

  setTimeout(*function* () {

    console.log(2);

  }, 0);

  console.log(3);

  // 执行结果:1;3;2;

这段代码的setTimeout里面的代码是在最后才执行的。这是因为setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式(W3CSchool对SetTImeout的定义)。按照定义来看,执行结果也应该是1;2;3; ,然而实际结果却是1;3;2;。这是为什么呢?这就要从浏览器对代码的执行机制来说起了。

那么,来看看下图:

image

(上图转自Philip Roberts的演讲《Help, I'm stuck in an event-loop》

我们来看几个摘自MDN的专业术语

heap(堆):对象被分配在堆中,堆是一个用来表示一大块(通常是非结构化的)内存区域的计算机术语。

stack(栈):函数调用形成了一个由若干帧组成的栈。

WebAPIS:囊括 Web 强大脚本能力的每个 API 参考资料, 包括 DOM 、所有相关的 APIs 及可以用来构建 Web 的相关接口。

队列(event queue):一个 JavaScript 运行时包含了一个待处理消息的消息队列。每一个消息都关联着一个用以处理这个消息的回调函数。

—— 摘自MDN

因为javascript是单线程的脚本语言,代码自上而下执行,代码在执行时会被压入执行栈(stack)中,当遇到setTimeout时会将setTimeout函数交给Web API来维护,当异步任务(比如:setTimeout)执行完成后会将对应的回调函数推入事件队列(event queue)中,当执行栈中任务全部执行完成之后浏览器会读取任务队列,把对应的回调函数再压入执行栈中,然后循环执行。这就是所谓的EventLoop。我们再来看看下图,执行结果会是怎样的呢?

image

执行结果:1;2;3;7;5;4;6;

为什么会得到这样的执行结果呢?为什么不是1;2;3;7;4;5;6;呢?这里涉及到了Macrotask 和 Microtask,即宏任务队列和微任务队列。setTimeout属于Macrotask,而Promise属于Microtask,从上图执行结果可以看出Microtask优先级高于Macrotask,当执行栈全部为空时,先询问是否有微任务,如果有,先执行为任务,全部执行完成后再执行宏任务。

我们再来看看下面这段代码的执行结果:

image

执行结果:1;2;3;8;5;6;4;7;

与Macrotask有所不同的是,Microtask中的任务不会一个一个压入执行栈中,而是直接压入执行栈,从上图执行结果可以得到佐证。Promise即使放入另外一个Promise的回调函数里,也会先执行Promise的回调,再执行setTimeout的回调。

你可能感兴趣的:(前端的经典的面试题——Event Loop(事件循环))