调用栈CallStack正式讲解了任务队列和事件循环,对JavaScript的工作原理有了一个大概的了解:当JavaScript运行时,主线程会形成一个栈,主要是解释器用来终结函数执行流程的一种机制。通常这个栈叫做调用栈CallStack,或者执行栈(ExecutionContextStack)。顾名思义,调用堆栈具有LIFO(后进先出)结构。调用堆栈存储代码执行期间的所有执行上下文。每次调用函数时,解释器都会将函数的执行上下文添加到调用栈中并开始执行;如果调用栈中正在执行的函数调用了其他函数,新函数也会被加入到调用栈中,并立即执行;当前函数执行完成后,解释器会从调用栈中清除其执行上下文,并继续执行剩余执行上下文中的剩余代码;但分配的调用堆栈空间已满,这将导致“堆栈溢出”错误。CallStack调用栈参考文章:1.juejin.cn/post/696902…[1]2.blog.csdn.net/ch834301/ar…[2]1、为什么需要任务队列和循环事件?1.JavaScript是单线程的:一次只能运行一个任务。通常,这没什么大不了的,但现在假设您正在运行一个需要30秒的任务,例如请求数据、计时器、读取文件等。在这个任务中,我们等待30秒再做任何其他事情(默认情况下,JavaScript运行在浏览器的主线程上,因此整个UI会卡住),后续语句必须等待前面的语句执行完成后才会开始执行。现在是2021年,没有人愿意被一个缓慢、反应迟钝的网站困住。2、浏览器的每个渲染进程都有一个主线程,主线程很忙。它不仅要处理DOM,还要计算样式,还要处理布局。它还需要处理JavaScript任务和各种输入事件。要让这么多不同类型的任务在主线程中有序的执行,这就需要一个系统来协调和调度这些任务。这个整体的调度系统就是我们今天要说的消息队列和事件循环系统。(不知道浏览器渲染时进程线程如何运行的同学,等我下一篇文章总结,后面会补上文章链接)3.如果想在线程运行过程中接收并执行新的任务,你必须要有一个事件循环机制。4.为了能够接收其他线程发送的消息,一个常见的模式是使用消息队列。同步任务和异步任务因此,JavaScript将所有执行任务分为同步任务和异步任务。其实我们的每一个任务都在做两件事,即发起调用和获取结果。同步任务和异步任务的主要区别是,同步任务调用后,很快就能得到结果,而异步任务不能马上得到结果,比如请求一个接口,每个接口都会有一定的响应时间,根据网络速度、服务器等因素决定。再比如定时器,它会在固定的时间后返回结果。因此,同步任务和异步任务的执行机制也不同。同步任务的执行其实和前面的案例是一样的。支持按照代码顺序和调用顺序进入调用栈并执行。执行完成后,调用栈被移除。对于一个异步任务的执行,首先还是会进入调用栈,然后发起一个调用,然后解释器会把它的响应回调任务放到一个任务队列中,然后调用栈会移除这个任务。当主线程清空,即所有同步任务执行完毕后,解释器会读取任务队列,将完成的异步任务依次添加到调用栈中执行。这里有一个重点,就是异步任务不会直接进入任务队列,而是将执行到异步函数(任务)的回调函数压入任务队列。img-blog.csdnimg.cn/20210629235...[3]这里还有一个知识点是关于任务入驻的。当任务进入任务队列后,实际上会使用浏览器的其他线程。尽管JavaScript是一种单线程语言,但浏览器并不是单线程的。不同的线程会处理不同的事件,当对应的事件可以执行时,对应的线程就会将其放入任务队列中。js引擎线程:用于解释和执行js代码、用户输入、网络请求等;GUI渲染线程:绘制用户界面,与JS主线程互斥(因为js可以操作DOM,会影响GUI的渲染结果);http异步网络请求线程:处理用户的get、post等请求,返回结果后将回调函数推入任务队列;定时触发线程:setInterval和setTimeout等待时间结束后,将执行函数推入任务队列;浏览器事件处理线程:点击、鼠标等UI交互事件发生后,将要执行的回调函数放入事件队列。此处插入图片描述2.任务队列和循环事件到底是什么?1.消息(任务)队列消息队列是一种数据结构,可以存储要执行的任务。它符合队列的“先进先出”特性,也就是说,如果要添加一个任务,就把它添加到队列的尾部;如果你想取出一个任务,从队列的头部取出它。在任务队列中,其实分为宏任务队列(TaskQueue)**和**微任务队列(MicrotaskQueue),分别存放宏任务和微任务。首先,宏任务和微任务都是异步任务。补充知识点:1、常用宏任务:script(整体代码)setTimeoutsetIntervalI/OUI交互事件postMessageMessageChannelsetImmediate(Node.js环境)2、常用微任务:Promise.thenObject.observeMutaionObserver进程。nextTick(Node.js环境)2.事件循环系统事件循环系统负责监控和执行消息队列中的任务。3.任务队列和循环事件如何使用事件循环EventLoop其实就是宏任务队列和微任务队列的执行,都是事件循环的一部分,这里就说说吧。事件循环的具体过程如下:从宏任务队列中,按照入队顺序找到第一个要执行的宏任务,放入调用栈,开始执行;执行完宏任务下的所有同步任务后,调用栈被清空之后,宏任务被压出宏任务队列,然后微任务队列开始按照入队顺序依次执行微任务,直到微任务队列被清空;当微任务队列清空时,事件循环结束;在任务队列中,找到下一个要执行的宏任务,开始第二次事件循环,直到宏任务队列清空。这里有几个要点:当我们第一次执行时,解释器会将整个代码脚本放入宏任务队列中,所以事件循环从第一个宏任务开始;如果在执行microtasks的过程中,产生一个新的microtask加入到microtask队列中,也需要一起清空;直到微任务队列被清空,下一个宏任务才会被执行。4、宏任务详解(如:setTimeout())为了协调这些任务在主线程上有序执行,页面进程引入了消息队列和事件循环机制,多个消息队列是渲染进程内部维护,如(延迟执行队列和普通消息队列)。然后主线程用一个for循环不断的从这些任务队列中取出任务并执行。我们将这些消息队列中的任务称为宏任务。当我们第一次执行时,解释器会将整体代码脚本放入宏任务队列中,因此事件循环从第一个宏任务开始;如果在微任务执行过程中有新的微任务被加入到微任务队列中,也需要一起清空;直到微任务队列被清空,下一个宏任务才会被执行。参考文章:1.juejin.cn/post/696902...[5]5、microtasks详解(如:promise、MutationObserver)microtasks是一个需要异步执行的函数。执行时机是在main函数执行完之后。在任务结束之前。我们知道,当JavaScript执行一个脚本时,V8会为其创建一个全局的执行上下文。在创建全局执行上下文的同时,V8引擎也会在内部创建一个微任务队列。顾名思义,这个microtaskqueue是用来存储microtasks的,因为有时候当前macrotask的执行过程中会产生多个microtasks,需要用这个microtaskqueue来保存这些microtasks。但是,这个微任务队列是V8引擎内部使用的,所以你不能直接通过JavaScript访问它。也就是说,每个宏任务都关联一个微任务队列。那么,接下来,我们需要分析两个重要的时间点——microtask产生的时机和microtask队列执行的时机。我们先来看看microtasks是怎么产生的?在现代浏览器中,有两种生成微任务的方法。第一种方式是使用MutationObserver监控某个DOM节点,然后通过JavaScript修改该节点,或者为该节点添加或删除一些子节点。当DOM节点发生变化时,会产生一个DOM变化记录的微任务。第二种方式是使用Promise。当调用Promise.resolve()或Promise.reject()时,也会产生微任务。好了,现在微任务队列中有了微任务,接下来就是看微任务队列什么时候执行了。通常,当当前宏任务中的JavaScript即将执行时,即JavaScript引擎即将退出全局执行上下文并清除调用堆栈时,JavaScript引擎会检查全局执行上下文中的微任务队列,然后依次执行队列中的Microtasks。WHATWG指的是microtasks执行的时间点作为checkpoints。当然,除了退出全局执行上下文的检查点之外,还有其他的检查点,不过都不是太重要,这里就不介绍了。如果在微任务执行过程中产生了新的微任务,微任务也会被加入到微任务队列中,V8引擎会继续执行微任务队列中的任务,直到队列为空。也就是说,在microtask执行过程中产生的新的microtask不会被推迟到下一个macrotask中,而是会在当前macrotask中继续执行。演示案例:该图是执行一个ParseHTML宏任务。在执行过程中,如果遇到JavaScript脚本,就会暂停解析过程,进入JavaScript执行环境。从图中可以看出,全局上下文包含一个微任务列表。在后续的JavaScript脚本执行过程中,分别通过Promise和removeChild创建了两个微任务,并添加到微任务列表中。然后JavaScript的执行结束,全局执行上下文即将退出。这个时候就到了检查点。JavaScript引擎会检查microtask列表,发现microtask列表中有microtasks,然后依次执行这两个microtasks。清空微任务队列后,退出全局执行上下文。image-20210630135706952注意点:microtasks和macrotasks是绑定的,每个macrotasks在执行时都会创建自己的microtasks队列。microtask的执行时长会影响当前macrotask的执行时长。比如在执行一个宏任务的过程中,产生了100个微任务,执行每个微任务的时间是10毫秒,那么执行这100个微任务的时间就是1000毫秒。也可以说,这100个微任务让宏任务的执行时间延长了1000毫秒。所以大家在写代码的时候一定要注意控制microtasks的执行时间。在一个macrotask中,分别创建一个macrotask和一个microtask用于回调。在任何情况下,微任务都比宏任务早执行。参考文章:1.time.geekbang.org/column/arti...[6]6.详解async和awaitasync会将后续函数(函数表达式或Lambda)的返回值封装到一个Promise对象中,await会等待Promisefulfill并返回其resolve的结果。ES7引入了一种在JavaScript中添加异步行为的新方法,使使用promises变得更加容易!通过引入async和await关键字,我们可以创建一个隐式返回promise的async函数。但是我们该怎么做呢?早些时候,我们看到我们可以使用Promise对象显式创建一个Promise,方法是输入newPromise(()=>{})、Promise.resolve或Promise.reject。我们现在可以创建隐式返回对象的异步函数,而不是显式使用Promise对象!这意味着我们不再需要编写任何Promise对象。Image虽然异步函数隐式返回承诺是一个了不起的事实,但只有在使用await关键字时才能看到异步函数的真正威力。当我们等待await之后的值返回一个resolvedpromise时,我们可以通过await关键字挂起异步函数。如果我们想获取resolvedpromise的值,就像我们之前使用then回调一样,我们可以将awaitedpromise的值赋值给一个变量!具体案例请参考以下五星文章,image.png五星提醒必读文章:1.惊艳!Visualjs:动态图演示Promises&Async/Await的流程!mp.weixin.qq.com/s\?\_\_biz=MzA…[7]2.厉害了!Visualjs:动态图演示-EventLoopblog.csdn.net/ch834301/ar...[8]原文地址:dev.to/lydiahallie...[9]原作者:LydiaHallie一个js函数的简单执行过程(简单总结):一个js函数的简单执行过程:先执行函数中的同步方法,所有同步任务完成后,再执行microtask的回调函数执行,如:varnum=10,console.log('timeout'),所有微任务的回调函数都被执行,如:Promise.resolve(5).then(res=>res_2).then(res=>res_2)最后在这个函数中执行macrotask的回调函数。例如:setTimeout(()=>{console.log('timeout')},0)(前提:如果有不同的任务,则不会执行)---
