当前位置: 首页 > Web前端 > HTML

JS异步与EventLoop

时间:2023-03-28 17:47:04 HTML

文章开始。请问大家,什么是异步?为什么需要异步?我想很多人的答案都会是setTimeout、Promise、asyncawait等等;但其实异步是一个概念,setTimeout、Promise、asyncawait只是执行异步的方法;我们都知道JS是一种单线程语言,也就是说,我们在JS代码中输入的代码,会以任务的形式从前到后,从上到下进行。如果要进行下一个任务,则需要完成上一个任务;如果一个任务花费的时间太长,它会导致页面冻结。我们可以称这种运行方式为同步方式或同步编程;如果我们可以让两个任务同时进行,而不是一个任务完成后再进行下一个任务,这样的卡顿现象是不是可以解决或者缓解呢?我们称这种想法为异步模式或异步编程。为了更好的理解事件循环(EventLoop),我们需要先了解一下什么是异步宏任务,什么是异步微任务。异步宏任务和异步微任务。线程语言,也就是说我们在页面中的任务事件最终都是在主线程中运行的。这些事件包括页面渲染、用户交互、js脚本执行、网络请求、文件读写等;为了协调这些任务在主线程上有序执行,页面进程引入了消息队列和事件循环机制,渲染进程内部维护了多个消息队列,如延迟执行队列和普通消息队列。然后主线程使用for循环不断从这些任务队列中取出任务并执行。我们将这些消息队列中的任务称为宏任务。宏任务分为同步宏任务和异步宏任务。我们主线程执行栈中的同步代码可以称为同步宏任务;那么异步宏任务有哪些呢?常见的异步宏任务包括:setTimeoutsetInterval文件读取dom事件(包括滚动、点击、鼠标移动等)那么什么是微任务呢?通俗地说,需要异步执行的回调函数的常见微任务有哪些?主要包括Promise类方法和Promise对象方法,asyncawait中的await等;什么是事件循环?简单的说,JS有一套特殊的机制来处理JS运行中任务的收集、排队和执行。我们把这种处理机制称为事件循环(EventLoop)。我们都知道浏览器执行js代码是在栈内存(stack)中执行的,假设有一段代码包含同步代码和异步代码,异步代码分为宏任务和微任务,那么这段代码是如何执行的在浏览器中?执行顺序是什么?首先,一段代码在执行时,会开辟两段内存,即堆内存和栈内存;然后它会创建两个队列,分别是任务监听队列WebAPI和任务队列EventQueue,然后浏览器会优先将执行栈中的同步代码(同步宏任务)从上到下执行;如果在执行过程中遇到异步任务,则将异步任务放入WebAPI的任务监控队列中进行监控。当检测到异步任务可执行时,异步代码会被放入任务队列EventQueue中等待;当同步代码执行时,任务队列EventQueue中的代码会被推送到主线程中依次执行(先进先出原则);这里需要说明的是任务队列EventQueue分为宏任务队列宏任务队列和微任务队列因为微任务队列的优先级比较高,所以微任务队列中的任务会被推送到主队列线程先运行,宏任务队列才会运行队列中的任务;因为浏览器是单线程运行的,同步代码、微任务代码、宏任务代码都在一个线程中运行;所以如果宏任务或者微任务代码在这个过程中被阻塞,就会影响执行栈向下运行;这就是事件循环机制,所以执行顺序是:同步任务->微任务->宏任务。运行完机制我们做一道题来巩固一下://blocking方法,用于js阻塞//delayTime单位毫秒函数wait(delayTime){letnowStamp=newDate().getTime()constendTime=nowStamp+delayTimewhile(true){if(nowStamp{console.log('setTimeout1')},100)constp1=newPromise(resolve=>{console.log('p1')resolve()}).then(()=>{console.log('then1')})constp2=newPromise(resolve=>{console.log('p2')resolve()}).then(()=>{setTimeout(()=>{console.log('setTimeout2')},100)})setTimeout(()=>{constp3=newPromise(resolve=>{console.log('p3')}).then(()=>{console.log('then3')})},100)wait(4000)setTimeout(()=>{console.log('setTimeout4')},5000)setTimeout(()=>{console.log('setTimeout3')},100)console.log('sync2')输出结果为:`sync1p1p2sync2then1setTimeout1p3setTimeout3setTimeout2setTimeout4`在这道题中包括promise、setTimeout、主线程代码和主线程阻塞;我们从上往下解读:当我们运行主线程代码时,会先输出sync1,然后setTimeout(()=>{console.log('setTimeout1')},800)会被推送到WepAPI进行监听因为是异步宏任务,100毫秒后会被推入eventQueue中的异步宏任务队列,等待主线程和异步微任务队列执行完毕;再往下constp1=newPromise(resolve=>{console.log('p1')resolve()}).then(()=>{console.log('then1')})因为p1是同步的task,所以会被主线程丢掉,然后1会被推到异步微任务队列等待主线程完成;constp2=newPromise(resolve=>{console.log('p2')resolve()}).then(()=>{setTimeout(()=>{console.log('setTimeout2')},100)})如上,会先执行主线程代码输出p2,然后setTimeout(()=>{console.log('setTimeout2')},100)push到microtask队列中再往下setTimeout(()=>{constp3=newPromise(resolve=>{console.log('p3')})},100)宏任务,推送到WebAPI,100毫秒后推送到异步宏任务队列wait(4000)阻塞4000毫秒,同步代码不会向下执行,此时setTimeout1已经被推入宏任务执行队列setTimeout(()=>{console.log('setTimeout4')},5000)执行完后进入WebAPI5000毫秒后阻塞并进入异步宏任务队列;setTimeout(()=>{console.log('setTimeout3')},100)执行block后进入WebAPI,100毫秒后进入异步宏任务队列;console.log('sync2')同步代码,直接输出这时候我们直接输出的同步代码是sync1p1p2sync2他们会按顺序在浏览器的微任务队列中输出的代码是then1和宏任务setTimeout(()=>{console.log('setTimeout2')},100)在P2的then方法中,这里会pushsetTimeout2进入WebApi,100毫秒后push到异步宏任务队列中。异步微任务输出结果为sync1p1p2sync2then1,然后我们执行异步宏任务队列中的任务;因为webApi监听和推送的时间不一样,这里这个时候我们在异步宏任务队列中的顺序是setTimeout1p3setTimeout3setTimeout2setTimeout4依次输出,最后的结果是`sync1p1p2sync2then1setTimeout1p3setTimeout3setTimeout2setTimeout4`