首页 > 编程语言 >javascript事件循环机制及面试题详解

javascript事件循环机制及面试题详解

时间:2023-09-12 10:36:20浏览次数:52  
标签:面试题 setTimeout console log 队列 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。

创建一个promise的实例就是开启了一个异步任务,传入的回调函数,也叫做excutor 函数,会立刻执行,所以输入promise,使用resolve返回一个成功的执行结果,then函数里的执行会推入到微任务队列中等待调用栈执行完成才依次执行。   向下执行发现定义了一个func1的函数,函数此时没有被调用,则不会推入调用栈中执行。程序继续往下,发现调用setTimeout函数,将打印的setTimeout推入宏任务队列,再往下执行调用函数func1,将func1推入调用栈中,执行func1函数,此时输出fun1。   调用栈里所有的内容都执行完成,开始轮询微任务队列,输入then回调5,最后执行宏任务队列,输入setTimeout   所以最后的输出结果为: 复制代码
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、调用栈执行完成后,会不断的轮询微任务队列,即使先将宏任务推入队列,也会先执行微任务

标签:面试题,setTimeout,console,log,队列,javascript,详解,执行,函数
From: https://www.cnblogs.com/sexintercourse/p/17695359.html

相关文章

  • Vue.js的index.html文件中引入JavaScript文件
    将js文件放在public文件夹下面在index.html文件下引入js文件在前面加<%=BASE_URL%>后面加路径,如果想将本地js文件打包之后也放在static/js文件夹下,需要在public文件夹下创建一个和打包之后文件放的位置一样的文件夹<scriptsrc="<%=BASE_URL%>./static/js/js文件名"></sc......
  • javascript:window.print() 打印
    1.JavaScript打印<inputid="btnPrint"type="button"value="button"οnclick="javascript:window.print();"style="color:#00f;font-weight:bold;text-decoration:none;cursor:pointer!important;cursor:hand"/>......
  • ES13 中11个令人惊叹的 JavaScript 新特性
    前言与许多其他编程语言一样,JavaScript也在不断发展。每年,该语言都会通过新功能变得更加强大,使开发人员能够编写更具表现力和简洁的代码。小编今天就为大家介绍ES13中添加的最新功能,并查看其用法示例以更好地理解它们。1.类在ES13之前,类字段只能在构造函数中声明。与许多其他......
  • Redis.conf 详解
    一、NETWORK网络bind127.0.0.1#绑定的IPprotected-modeno#保护模式port6379#端口设置二、GENERAL通用daemonizeyes#以守护进程的方式运行,默认是no,我们需要自己开启为yespidfile/var/run/redis_6379.pid#如果是后台启动,我们需要指定一个pid文......
  • C++算法之旅、06 基础篇 | 第四章 动态规划 详解
    常见问题闫式DP分析法状态表示集合满足一定条件的所有方案属性集合(所有方案)的某种属性(Max、Min、Count等)状态计算(集合划分)如何将当前集合划分成多个子集合状态计算相当于集合的划分:把当前集合划分成若干个子集,使得每个子集的状态可以先算出来,从而推导当前......
  • realloc() 用法详解
    在开发过程中我们经常要动态地进行内存分配,而内存的管理是一个重要的问题。在C语言中,内存分为四个区域:堆区,栈区,全局/静态存储区和常量存储区。其中,堆区用于动态内存分配。在C标准库中,主要有两个函数用于动态内存分配,分别是malloc()和realloc()。malloc()函数malloc()函数在堆内......
  • Sentinel 工作原理详解
    1综述Sentinel所有资源都对应:一个资源名称一个EntryEntry可通过:对主流框架适配自动创建也可通过注解的方式或调API显式创建每个Entry创建同时也会创建一系列功能插槽(slotchain)。这些插槽有不同职责如:NodeSelectorSlot负责收集资源的路径,并将这些资源的......
  • 【愚公系列】2023年09月 WPF控件专题 Canvas控件详解
    (文章目录)前言WPF控件是WindowsPresentationFoundation(WPF)中的基本用户界面元素。它们是可视化对象,可以用来创建各种用户界面。WPF控件可以分为两类:原生控件和自定义控件。原生控件是由Microsoft提供的内置控件,如Button、TextBox、Label、ComboBox等。这些控件都是WPF中常见......
  • 无涯教程-JavaScript - ODDFPRICE函数
    描述ODDFPRICE函数返回面值为$100的第一期奇数(短期或长期)证券的价格。语法ODDFPRICE(settlement,maturity,issue,first_coupon,rate,yld,redemption,frequency,[basis])争论Argument描述Required/OptionalSettlement证券的结算日期。证券结算日期是指在......
  • 无涯教程-JavaScript - NPV函数
    描述NPV函数通过使用折现率以及一系列未来付款(负值)和收入(正值)来计算投资的净现值。语法NPV(rate,value1,[value2],...)争论Argument描述Required/OptionalRateTherateofdiscountoverthelengthofoneperiod.RequiredValue11to254argumentsrepresen......