028-86261949

当前位置:首页 > 技术交流 > 经典的面试题——Event Loop(事件循环)

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

2020/09/28 11:57 分类: 技术交流 浏览:0

我们今天来说说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;。这是为什么呢?这就要从浏览器对代码的执行机制来说起了。

 

那么,来看看下图:

 

(上图转自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。我们再来看看下图,执行结果会是怎样的呢? 

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

 

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

 

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

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

 

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

 

#标签:H5,前端,面试题