IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> javascript事件循环机制及面试题详解 -> 正文阅读

[JavaScript知识库]javascript事件循环机制及面试题详解

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

首先是调用栈,执行耗时较短的操作,耗时较长的操作先放置到任务队列中,任务队列又分为宏任务(macro-task)和微任务(micro-task),微任务中队列中放置的是 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、调用栈执行完成后,会不断的轮询微任务队列,即使先将宏任务推入队列,也会先执行微任务

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-13 17:22:27  更:2021-07-13 17:23:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/5 0:25:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码