笔试题——JavaScript事件循环机制(event loop、macrotask、microtask)

今天作了一道笔试题以为颇有意义分享给你们,题目以下:html

setTimeout(()=>{
    console.log('A');
},0);
var obj={
    func:function () {
        setTimeout(function () {
            console.log('B')
        },0);
        return new Promise(function (resolve) {
            console.log('C');
            resolve();
        })
    }
};
obj.func().then(function () {
    console.log('D')
});
console.log('E');复制代码

JavaScript 都知道它是一门单线程的语言,这也就意味着 JS 没法进行多线程编程,可是 JS 当中却有着无处不在的异步概念 。要彻底理解异步,就须要了解 JS 的运行核心——事件循环(event loop)。
前端

 1、什么是事件队列?

首先来看一个小小的demoes6

console.log('start');
setTimeout(()=>{
    console.log('A');
},1000);
console.log('end');
//start
//end
//A复制代码

js执行以后,程序输出 'start' 和 'end',在大约1s以后输出了 'A' 。那咱们就有疑问了?为何A不在end以前执行呢?
编程

这是由于 setTimeout 是一个异步的函数。意思也就是说当咱们设置一个延迟函数的时候,当前脚本并不会阻塞,它只是会在浏览器的事件表中进行记录,程序会继续向下执行。当延迟的时间结束以后,事件表会将回调函数添加至事件队列(task queue)中,事件队列拿到了任务事后便将任务压入执行栈(stack)当中,执行栈执行任务,输出 'A'。
promise

事件队列是一个存储着待执行任务的队列,其中的任务严格按照时间前后顺序执行,排在队头的任务将会率先执行,而排在队尾的任务会最后执行。事件队列每次仅执行一个任务,在该任务执行完毕以后,再执行下一个任务。执行栈则是一个相似于函数调用栈的运行容器,当执行栈为空时,JS 引擎便检查事件队列,若是不为空的话,事件队列便将第一个任务压入执行栈中运行。
浏览器

那么我将这个例子作一个小小的改动看一看:bash

console.log('start');
setTimeout(()=>{
    console.log('A');
},0);
console.log('end');
//start
//end
//A复制代码

能够看出,咱们将settimeout第二个参数设置为0后,'A' 也老是会在 'end' 以后输出。因此究竟发生了什么?这是由于 setTimeout 的回调函数只是会被添加至事件队列,而不是当即执行。因为当前的任务没有执行结束,因此 setTimeout 任务不会执行,直到输出了 'end' 以后,当前任务执行完毕,执行栈为空,这时事件队列才会把 setTimeout 回调函数压入执行栈执行。
多线程

 2、Promise的含义和基本用法?

所谓Promise,简单说就是一个容器,里面保存着某个将来才会结束的事件(一般是一个异步操做)的结果。从语法上说,Promise 是一个对象,从它能够获取异步操做的消息。Promise 提供统一的 API,各类异步操做均可以用一样的方法进行处理。
异步

写一个小demo看一下Promise的运行机制:函数

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');
// Promise
// Hi!
// resolved复制代码

上面代码中,Promise 新建后当即执行,因此首先输出的是Promise。而后,then方法指定的回调函数,将在当前脚本全部同步任务执行完才会执行,因此resolved最后输出。

 3、Macrotasks和Microtasks

Macrotasks和Microtasks 都属于上述的异步任务中的一种,他们分别有以下API:
macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering
microtasks: process.nextTick, Promise, MutationObserver

setTimeout的macrotask, 和 Promise的microtask 有哪些不一样,先来看下代码以下:

console.log(1);
setTimeout(function(){
  console.log(2);
}, 0);
Promise.resolve().then(function(){
  console.log(3);
}).then(function(){
  console.log(4);
});

//1
//3
//4
//2复制代码

如上代码能够看到,Promise的函数代码的异步任务会优先setTimeout的延时为0的任务先执行。

缘由是任务队列分为 macrotasks 和 microtasks, 而promise中的then方法的函数会被推入到microtasks队列中,而setTimeout函数会被推入到macrotasks

任务队列中,在每一次事件循环中,macrotask只会提取一个执行,而microtask一直提取,直到microsoft队列为空为止。

也就是说若是某个microtask任务被推入到执行中,那么当主线程任务执行完成后,会循环调用该队列任务中的下一个任务来执行,直到该任务队列到最后一个任务为止。

而事件循环每次只会入栈一个macrotask,主线程执行完成该任务后又会检查microtasks队列并完成里面的全部任务后再执行macrotask的任务。

 4、分析本题目

setTimeout(()=>{
    console.log('A');
},0);
var obj={
    func:function () {
        setTimeout(function () {
            console.log('B')
        },0);
        return new Promise(function (resolve) {
            console.log('C');
            resolve();
        })
    }
};
obj.func().then(function () {
    console.log('D')
});
console.log('E');复制代码

一、首先 setTimeout A 被加入到事件队列中  ==>  此时macrotasks中有[‘A’];

二、obj.func()执行时,setTimeout B 被加入到事件队列中  ==> 此时macrotasks中有[‘A’,‘B’];

三、接着return一个Promise对象,Promise 新建后当即执行 执行console.log('C'); 控制台首次打印‘C’;

四、而后,then方法指定的回调函数,被加入到microtasks队列,将在当前脚本全部同步任务执行完才会执行。 ==> 此时microtasks中有[‘D’];

五、而后继续执行当前脚本的同步任务,故控制台第二次输出‘E’;

六、此时全部同步任务执行完毕,如上所述先检查microtasks队列完成其中全部任务,故控制台第三次输出‘D’;

七、最后再执行macrotask的任务,而且按照入队列的时间顺序,控制台第四次输出‘A’,控制台第五次输出‘B’。

 5、执行js代码



分析与实际符合,NICE!

参考文章:www.cnblogs.com/tugenhua070…

还有阮老师的promise介绍:es6.ruanyifeng.com/?search=pro…

文章本人原创,转载请评论;

前端菜鸟对JavaScript的理解还有不少不足,若有错误欢迎你们指出来;

喜欢的点个赞把!

相关文章
相关标签/搜索