JavaScript 在浏览器中的事件循环

avatarplhDigital nomad

关于js事件

你应该看看image

什么是call stack(调用栈)

  • queues: 先入先出(js和settimeout和gui和时间等线程按照队列形式执行,即先入先出顺序)
  • stack: 后入先出(而调用栈则按照后入先出的顺序依次执行,明白这个道理对后续的异步函数学习很有帮助)

所有被调用的js函数都会被放入栈,按照后入先出的顺序依次执行。理解这个对于理解js异步函数非常重要。JavaScript(引擎)是单线程的,Event loop并不属于JavaScript本身,但JavaScript的运行环境是多线程/多进程的,运行环境实现了Event loop。,js的单线程执行完美避开了GUI(css图形界面渲染)冲突,但同时js的运行环境又是多线程的,常见的线程有js主线程,GUI图形界面绘制进程,点击事件等进程,setTimeout/setInterVal时间进程,这么多进程共同协作,才构成了js灵活多变的浏览器操作。这样的设计异常巧妙。而html5又加入了web worker,但它是另一个进程,和我们所讨论的互不干涉,es6又加入了promise.then,但是promise真的不属于多线程,promise内部原理我是理解不了。 但是请看下图,

  • 1.最先被调用的是正常的js闭包只执行代码。
  • 2.排第二的是nodejs里面的process.nextTick()。
  • 3.排第三的则是promise().then()返回的代码,你可以将他理解做另一个线程,但他一定排在setTimeout线程前面。
  • 4.再就是js标准里面的setTimeout了,他就是另一个线程。
  • 5.再就是nodejs里面的setImmediate,他被放在了最后。

image

再次科普一下promise setTimeout

  • 宏任务主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、setImmediate(Node.js 环境)
  • 微任务主要包含:Promise、MutaionObserver、process.nextTick(Node.js 环境)

我们的JavaScript的执行过程是单线程的,所有的任务可以看做存放在两个队列中——执行队列和事件队列。

执行队列里面是所有同步代码的任务,事件队列里面是所有异步代码的宏任务,而我们的微任务,是处在两个队列之间。

当JavaScript执行时,优先执行完所有同步代码,遇到对应的异步代码,就会根据其任务类型存到对应队列(宏任务放入事件队列,微任务放入执行队列之后,事件队列之前);当执行完同步代码之后,就会执行位于执行队列和事件队列之间的微任务,然后再执行事件队列中的宏任务。

为了再次深入了解promise 和 settimeout和setImmediate,请查看如下代码

image 😴醉了,根本不是线程的问题,promise内部是保证一定完成,才进行下一步,所以必须先将promise内部函数执行完毕,才进行then后的函数,而then则属于微任务,所以它不是setTimeout和setImmediate这种宏任务的的对手,setTimeout线程位于setImmediate线程后面,所以then里面setImmediate先执行,setTimeout后执行

再看一个例子

我将promise命名一个字面量,输出的结果却又不一样了。真令人困惑。promise被分叉成两个.then image 同样的函数我执行5次,如下图,居然发现输出结果不一致,为什么呢??? image 发现规律就是1永远排在最前面,246为一组,35为一组,这两组不定期排序。。本身是不是说明了事情就是promise的这两个单独的then让promise分叉了,任意时间返回结果。。。但是35是setTimeout线程,而246则是setImmediate线程,settimeout线程不是永远应该排在setImmediate后面么。。。

从 Error看 调用栈 call stack

如图,请认真查看这个报错的stack trace栈追踪,事件执行顺序依然是 foo -> bar -> baz -> object,而这条追踪对应的是error发生的时候记录的栈追踪,非常有意思。 image 当我用node去调用他的时候,没有发生任何变化。 image 浏览器的报错栈树,最外层的anonymous则是最外层我们调用js本身的main.js,这属于浏览器的栈追踪。 image

从调用自身查看stack调用

老司机应该经常看到这种报错,超过最大限量的栈调用 image

Reference: