[译]JavaScript的调用栈、回调队列和事件循环

译者按
这篇文章能够看作是对Philip Roberts 2014年在JSConf演讲的 《What the heck is the event loop anyway?》的一个总结。
建议先看Philip Roberts的这个演讲而后再阅读本篇文章。这哥们儿的演讲语言幽默风趣,内容通俗易懂,很是值得一看。

在这个视频中,Philip Roberts将JavaScript的调用栈、回调队列和事件循环的内容讲的很清晰。因此你能够随意的跳过这篇文章,花上一个半小时去看视频。固然若是你愿意读一下个人这篇文章那也不是不能够。ajax

什么是JavaScript

什么是JavaScript呢?列举一些关键词就是:api

  • 他是单线程的、非阻塞的、异步的并发语言
  • 他有一个调用栈,一个事件循环,一个回调队列,还有一些api和别的东西

若是你像我同样(或者像Philip Roberts)对此懵逼的话,这些话自己并没不意味着什么。那咱们就来剖析一下。浏览器

JavaScript运行时

JavaScript运行时(像V8引擎)拥有一个堆(内存分配用的)和栈(执行上下文)。可是他没有setTimeoutDOM等。这些是浏览器提供的Web APIs网络

咱们了解的JavaScript

浏览器中的JavaScript拥有:数据结构

  • 一个像V8引擎同样的运行时(提供堆栈)
  • 浏览器提供的Web APIs,例如:DOMAJAXsetTimeout
  • 一个为各类事件回调准备的回调队列,例如:onClickonLoadonDone
  • 一个事件循环

clipboard.png

什么是调用栈

JavaScript是单线程的,意味着他有一个单独的调用栈,意味着他一次能作一件事。调用栈基本上就是一个记录程序执行位置的数据结构。若是程序进入了一个函数,那就往这个栈里面塞些东西。若是程序从一个函数中return了,那就从栈顶弹出一些东西。并发

当咱们的程序报错的时候,咱们会在控制台看到调用栈信息。报错的时候咱们能够看到栈的状态(被调用的那个函数的)。异步

阻塞

这涉及到一个重要的问题:程序运行的很慢的时候发生了什么?换句话说,就是程序阻塞了。阻塞并无严格的定义。实际上就是程序执行慢。执行console.log不慢,可是一个从1到1,000,000,000的while循环,图像处理或者网络请求这些操做的执行就比较费时了。这些执行慢的东西堆在一块儿就发生了阻塞。函数

由于JavaScript是单线程的,咱们发起一个网络请求就不得不一直等到他结束。这在浏览器中就是个问题--当咱们等这个请求的时候,浏览器就发生了阻塞(咱们不能作点击、提交表单等操做)。解决这个问题的方法就是使用异步回调。工具

并发,看到这个词的时候咱们会发现上面有一个地方说的不对

JavaScript一次只能作一件事情的说法是不对的。正确的说法应该是:JavaScript的运行时一次只能作一件事。他不能一边发ajax请求一边运行别的代码,也不能在执行别的代码时候运行一个定时器。可是咱们能够并发的作这些事。由于浏览器不单单是一个运行时(还记得上面那个渣渣画质的图吗?)。oop

调用栈能够往Web APIs里面放东西,Web APIs能够在事件结束的时候把回调函数放进回调队列,而后是事件循环。最终咱们进入事件循环,这是这个过程当中最简单的部分,他有一个很是简单的工做:看看调用栈,瞅瞅回调队列,若是调用栈空闲了,就把回调队列中的第一个函数取出来丢进调用栈让他执行(这就回到了JavaScript的地盘,回到了V8的内部)。

整个串起来

Philip搞了一个的碉堡的工具来可视化这个过程,这玩意儿叫Loupe。这是一个可以把JavaScript运行时可视化的工具。

咱们用它来看一个简单的例子:在一个异步的setTimeout回调中用console.log在控制台打些log出来。

clipboard.png

整个过程到底都发生了什么呢?咱们来看一下:

  1. 执行进入console.log('Hi');函数,所以这个函数被丢进了调用栈里。
  2. console.log('Hi');函数return了,所以他就被弹出了栈顶。
  3. 执行进入setTimeout函数,所以这个函数被丢进了调用栈里。
  4. setTimeoutWeb APIs的一部分,所以Web APIs处理了他,而且等了2秒
  5. 继续执行脚本,进入console.log('EvenyBody')函数,把他也丢进调用栈。
  6. console.log('EvenyBody')函数return了,因此把他从栈顶弹出去
  7. 2秒的定时已经完成了,因此就把对应的回调函数放到回调队列里。
  8. 事件循环检查调用栈是否为空,若是非空的话,他就等着。由于调用栈如今是空的,因此把回调队列中的回调函数丢进调用栈。
  9. console.log('There')函数返回了,所以把他从栈顶弹出去(译者按:原文为console.log('Everybody'),应为书写错误)。

有趣的一点是:setTimeout(function(...), 0)的状况。setTimeout为0的时候这个过程看起来可能不明显,除非考虑到调用栈的执行环境和事件循环的状况。基本上都会推迟到调用栈为空才执行。

考虑UI渲染的性能的状况

为了回到了咱们平常处理的UI层,咱们须要考虑渲染问题。浏览器受到咱们在JavaScript中所作操做的影响,他可能每隔16.6ms重绘一次屏幕(60帧/秒)。可是调用栈还有代码在执行的话,他其实是无法作重绘的。

就像Philip说的同样:

当你们说不要"阻塞事件循环"的时候,他们其实是说:不要把耗费时间长的代码放进调用栈,由于你要这么搞的话,浏览器就不能作他该作的事了,好比说给你搞一个漂亮流畅的UI。

Philip Roberts “What the Heck Is the Event Loop Anyway”

举个例子,滚动的处理函数触发多了会让UI变得卡顿。顺便说一句,这是我听过的对防抖最清楚的解释了,这就是你要作到的“不要阻塞事件循环”(那就是咱们只在滚动处理函数被触发x次后才执行那些耗时的操做)。

结语

总之,这就是《What the heck is the event loop anyway?》的答案。Philip的演讲很好的帮我理解了什么是JavaScript,什么不是,哪一个部分是运行时,哪一个部分是浏览器的和咱们该怎样有效的使用事件循环。好好看看这个视频吧。

相关文章
相关标签/搜索