关于前端:javascript事件循环机制及面试题详解

5次阅读

共计 3429 个字符,预计需要花费 9 分钟才能阅读完成。

javascript 是单线程执行的程序,也就是它只有一条主线,所有的程序都是逐行“排队”执行,在这种状况下可能存在一些问题,比如说 setTimeout、ajax 期待执行的工夫较长,就会阻塞后续代码的执行,使得整个程序执行的耗时十分久,那么为了应答这样一个问题,javascript 代码在执行的时候,是有几个“通道”的。

首先是调用栈,执行耗时较短的操作,耗时较长的操作先搁置到工作队列中,工作队列又分为宏工作和微工作,微工作中队列中搁置的是 promise.then、aysnc、await 这样操作,宏工作队列中搁置的是 setTimeout、ajax、onClick 事件,等调用栈的工作执行实现再轮询微工作队列,微工作队列中工作执行实现之后再执行宏工作。

这里提到了栈和队列,简略说一下这两种数据结构,栈是一种后进先出的构造,只能从尾部进入,从尾部删除,拿生存中的场景来打比方,就如同自助餐的餐盘,最先放的盘子在最底下,最初放的盘子在最下面,须要把最下面的盘子一个个拿走,能力拿到最上面的盘子。

而队列,是一种先进先出的构造,从尾部进入,从头部删除,就像咱们去排队买货色,先去的同学能够先买到。

再回到事件循环机制(event loop),不阻塞主过程的程序放入调用栈中,压入栈底,执行完了就会弹出,如果是函数,那么执行完函数里所有的内容才会弹出,而阻塞主过程的程序放入工作队列中,他们须要“排队”顺次执行。

首先来个简略的例子,判断以下程序的执行程序

new Promise(resolve => {console.log('promise');
  resolve(5);
}).then(value=>{console.log('then 回调', value)
})

function func1() {console.log('func1');
}

setTimeout(() => {console.log('setTimeout');
});

func1();

创立一个 promise 的实例就是开启了一个异步工作,传入的回调函数,也叫做 excutor 函数,会立即执行,所以输出 promise,应用 resolve 返回一个胜利的执行后果,then 函数里的执行会推入到微工作队列中期待调用栈执行实现才顺次执行。

向下执行发现定义了一个 func1 的函数,函数此时没有被调用,则不会推入调用栈中执行。程序持续往下,发现调用 setTimeout 函数,将打印的 setTimeout 推入宏工作队列,再往下执行调用函数 func1,将 func1 推入调用栈中,执行 func1 函数,此时输入 fun1。

调用栈里所有的内容都执行实现,开始轮询微工作队列,输出 then 回调 5,最初执行宏工作队列,输出 setTimeout

 再来看一个简单的例子

setTimeout(function () {console.log("set1");
  new Promise(function (resolve) {resolve();
  }).then(function () {new Promise(function (resolve) {resolve();
    }).then(function () {console.log("then4");
    });
    console.log("then2");
  });
});

new Promise(function (resolve) {console.log("pr1");
  resolve();}).then(function () {console.log("then1");
});

setTimeout(function () {console.log("set2");
});

console.log(2);

queueMicrotask(() => {console.log("queueMicrotask1")
});

new Promise(function (resolve) {resolve();
}).then(function () {console.log("then3");
});

setTimeout 执行的回调函数 (“set1”) 间接被搁置到宏工作队列中期待,Promise 的 excutor 函数立即执行,首先输出 pr1,Promise.then 函数 (“then1”) 放入微工作队列中期待,上面的 setTimeout 执行的回调函数 (“set2”) 也被搁置到宏工作队列中,排在 (“set1”) 前面,接下来调用栈中输入 2,queueMicrotask 示意开启一个微工作,与 Promise.then 函数成果统一,(“queueMicrotask1”)放入微工作队列中,再往下执行,new Promise 的 excutor 函数立即执行,then 函数 (“then3”) 放到微工作队列中期待,此时调用栈出已顺次输出 pr1,2。

调用栈中程序已执行完,来到微工作队列中执行微工作,顺次输入 then1,queueMicrotask1,then3。

此时微工作队列中的工作也执行实现,来到宏工作队列中,输入 set1,执行 Promise 的 excutor 函数,resolve 即返回胜利的执行后果,then 函数 (“then2”) 放入微工作中,一旦微工作队列中有工作,就不会往后执行宏工作,所以宏工作队列中的另一个 setTimeout 函数 (“set2”) 此时不会执行,来到微工作队列中执行 (“then2”),输入 then2,再执行一个 promise 函数,(“then4”) 被放入到微工作队列中,输入 then4。

微工作队列也都执行实现,此时来到宏工作队列中,执行 set2。

所以最初的输入后果为:

pr1
2
then1
queueMicrotask1
then3
set1
then2
then4
set2

简略图示如下

最初一道题,加上了 async、await

先来一个论断,通过 async 定义的函数在调用栈中执行,await 将异步程序变成同步,所以 await 前面执行的程序须要等到 await 定义的函数执行实现才执行,须要在微工作队列中期待

async function async1 () {console.log('async1 start')
  await async2();
  console.log('async1 end')
}

async function async2 () {console.log('async2')
}

console.log('script start')

setTimeout(function () {console.log('setTimeout')
}, 0)

async1();

new Promise (function (resolve) {console.log('promise1')
  resolve();}).then (function () {console.log('promise2')
})

console.log('script end')

函数只有调用的时候才会推入调用栈中,所以最先执行的是 console.log,即输入 script start,而后 setTimeout 函数 (“setTimeout”) 放入宏工作队列中期待,调用 async1 函数,输入 async1 start,执行 async2 函数,输入 async2,(“async1 end”)放入微工作队列中期待,持续向下执行 Promise 函数,输入 promise1,then 函数中的 (“promise2”) 放入微工作队列中期待,输入 script end。

调用栈的程序都已执行结束,此时开始执行微工作队列中的程序,顺次输入 async1 end、promise2。

微工作队列中的程序也已执行实现,开始执行宏工作中的程序,输入 setTimeout。

输入程序为

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

简略图示如下

判断执行程序能够记住以下几个重点

1、promise 中的回调函数立即执行,then 中的回调函数会推入微工作队列中,期待调用栈所有工作执行完才执行

2、async 函数里的内容是放入调用栈执行的,await 的下一行内容是放入微工作执行的

3、调用栈执行实现后,会一直的轮询微工作队列,即便先将宏工作推入队列,也会先执行微工作

正文完
 0