####1.阻塞事件循环任何花费太长时间将控制权返回事件循环的JavaScript代码都会阻塞页面中任何JavaScript代码的执行,甚至阻塞UI线程,用户无法点击、滚动页面等。几乎所有JavaScript中的I/O原语都是非阻塞的。网络请求、文件系统操作等。被阻止是一个例外,这就是为什么JavaScript如此多地基于回调(以及最近基于promise和async/await)的原因。####2.调用栈调用栈是一个LIFO队列(后进先出)。事件循环不断检查调用堆栈以查看是否需要运行任何函数。执行时,它将找到的所有函数调用添加到调用堆栈中,并按顺序执行每个函数。您知道在调试器或浏览器控制台中您可能熟悉的错误堆栈跟踪吗?浏览器在调用栈中查找函数名,告诉你是哪个函数发起了当前调用:####3.事件循环1.一个简单的事件循环例子:constbar=()=>console.log('bar')constbaz=()=>console.log('baz')constfoo=()=>{console.log('foo')bar()baz()}foo()此代码按预期工作正确打印:foobarbaz运行此代码时,首先调用foo()。在foo()内部,首先调用bar(),然后调用baz()。此时调用栈看起来是这样的:每次迭代中的事件循环查看调用栈中是否有东西并执行直到调用栈为空:2.入队函数执行上面的例子看起来很正常,没什么special:JavaScript寻找要执行的东西,并按顺序运行它们。让我们看看如何延迟函数直到堆栈被清空。setTimeout(()=>{},0)的用例是调用一个函数,但在代码中的所有其他函数都已执行之后。例如:constbar=()=>console.log('bar')constbaz=()=>console.log('baz')constfoo=()=>{console.log('foo')setTimeout(bar,0)baz()}foo()此代码将打印:foobazbar运行此代码时,将首先调用foo()。在foo()内部,首先调用setTimeout,将bar作为参数传递,并将0作为计时器传递以指示它尽快运行。然后调用baz()。此时,调用堆栈看起来像这样:这是程序中所有函数执行的顺序:为什么会这样?3.消息队列调用setTimeout()时,浏览器或Node.js会启动定时器。当计时器到期时(在本例中立即到期,因为超时值设置为0),回调函数被放入“消息队列”。在消息队列中,用户发起的事件(例如单击或键盘事件,或获取响应)也在代码有机会对其做出反应之前排队在这里。对于像onLoad这样的DOM事件也是如此。事件循环优先处理调用栈,它首先处理它在调用栈中找到的所有东西,一旦里面什么都没有,它就开始处理消息队列中的东西。我们不必等待setTimeout、fetch或其他函数完成它们的工作,因为它们由浏览器提供并存在于它们自己的线程中。比如你设置了setTimeout的超时时间为2秒,但是你不必等待2秒,等待就发生在别处。####4.ES6作业队列ECMAScript2015引入了作业队列的概念,它被Promise使用(在ES6/ES2015中也引入)。这样异步函数的结果会尽快执行,而不是在调用堆栈的末尾。在当前函数结束之前解决的Promise将在当前函数之后立即实现。有一个很好的比喻,就像游乐园里的过山车:消息队列让你排在队列的最后面(在其他人之后),你必须等待轮到你,而工作队列是快速通道票,所以你可以在完成上一次骑行后立即进行另一次骑行。示例:constbar=()=>console.log('bar')constbaz=()=>console.log('baz')constfoo=()=>{console.log('foo')setTimeout(bar,0)newPromise((resolve,reject)=>resolve('shouldbeafterbaz,beforebar')).then(resolve=>console.log(resolve))baz()}foo()这将打印:foobaz应该在baz之后,在bar之前这是Promises(以及基于promises的async/await)和通过setTimeout()或其他平台API的普通旧异步函数之间的巨大区别。
