Web 开发者,或者前端工程师(我们更喜欢别人这么称呼)现如今几乎能做所有的工作,从扮演一个浏览器内部交互性的角色,到制作电脑游戏、桌面控件、跨平台手机应用,甚至还可以把它写在服务器端(最流行的是node.js)和数据库连接——作为一个脚本语言,实现却近似无所不在。因此弄明白JavaScript的内部机制非常重要,这有助于我们更好地和更有效率的使用它,而这些就是本篇文章要讲的内容。

现在JavaScript生态变得比以往都要复杂,而且未来还会更加复杂。构建一个现代web应用可使用的工具有WebPack、Babel、ESLint、 Mocha、 Karma、 Grunt等等——这些工具我该用哪个呢,每个工具又是用来干嘛的呢?。我发现这个web漫画,生动的诠释了今天web开发者的内心的纠结。


在每个JavaScript开发者在一头扎进框架或者库的使用之前,首先需要做的就是知晓 如何在最根本的层面上实现所有这些的基础。几乎所有的JS开发者都听说过术语 “V8” 、Chrome的运行时,但一些人可能并不真的懂得他们的意义以及用处。最初我在从事开发工作的第一个年头,对这些花哨的术语也不太了解,因为更多的是先完成工作。而这并不能满足我对JavaScript是如何做到这些事情的好奇心。我决定深挖,查遍谷歌,然后发现好的博客文章很少。而这不多的有用信息中就包括一位Philip Roberts的大牛,及其视频到底什么是Event Loop呢? | 欧洲 JSConf 2014。因此我决定总结我在视频中所学到的并把它分享出来。因为有很多事情需要先做解释,我就把文章分为2个部分。本部分将介绍用到的术语,第二部分再把他们给串起来。

JavaScript是一门单线程单并发语言,意味着它一次只能处理一个任务,或者一次只运行一条代码。它有一个单独的调用栈(call stack),与堆、队列等其他部分一起构成Javascript并发模型(在V8中实现)。我们首先简要介绍下每个术语:


  1. 调用栈(Call Stack):调用栈是一个记录函数调用的数据结构。如果我们调用一个函数去执行,我们就会在这个栈中push东西。当我们从一个函数返回时,栈顶就pop出该函数。


当运行程序时,我们首先查找main函数——我们所有其他的函数都是在main函数中执行。如上面GIF图所示,运行首先开始于 console.log(bar(6)),因此其被push到栈中。下一帧是函数bar和他的参数,而bar调用函数foo,因此foo也被push到栈中。

foo 立即执行完成后返回,因此从栈顶弹出。类似的bar也从栈中弹出,最后是console弹出并打印输出。所有这些都发生在毫秒级的时间里。

我想你们一定见过浏览器控制台中有时会出现的红色错误堆栈跟踪,它基本上指示了调用栈的当前状态,而函数报错的从顶到底的方式和栈一样。(见下图)


有时候,在我们调用递归函数的时候会进入一个无线循环的情况,而Chrome浏览器限制栈的大小是16000帧,如果超出就会终止掉你的进程并弹出Max Stack Error Reached(见下图)


2. 堆(Heap):对象在堆中分配,即堆中的大部分是非结构化的内存区域。变量和对象的内存分配都发生在这里。

  1. 队列(Queue ):一个js 运行时包含一个消息队列,它是一个要处理的消息和相关要调用的函数的的列表。当栈有足够的容量时,从队列中取出消息并进行处理,该消息包括调用关联函数(从而创建初始堆栈帧)。当消息处理结束时,栈又变成了空的。简言之,这些消息是根据外部的异步事件(例如鼠标被单击或接收对HTTP请求的响应)排队的,因为已经提供了回调函数。如果,比如有人点击一个按钮,而按钮没有提供回调函数,就不会有消息去排队。

事件循环

总的说来,当我们评估js代码性能时,是栈中的函数来决定是快还是慢,console.log()运行很快,而执行for或者while进行大量迭代的函数则会慢得多,并且在执行时会保持堆栈被占用或阻塞。这就是你们在Webpage Speed Insights上听到或看到的术语:阻塞脚本。

网络请求可能会很慢,图片请求可能会很慢,但谢天谢地,服务器请求可以通过异步的AJAX完成。试想,假如这些网络请求是通过同步功能实现的,将会发生什么?。网络请求被发送到一些服务器上,它一般是另一台计算机/机器。现在,计算机可以很慢地回送响应。同时,如果单击某个按钮,或者需要执行其他渲染,当栈被阻塞时,就什么也做不了。在多线程语言像ruby,别的请求可以被处理。但在单线程语言像js,在栈中函数return一个值之前,别的请求想要被处理就显得不太现实。在浏览器不能做任何事时,网页就糟糕透顶。如果我们想要用户的体验流畅的UI,这是非常不理想的。那么,我们该怎么解决呢?

“Concurrency in JS— One Thing at a Time, except not Really, Async Callbacks”


最简单的方式就是使用异步回调,异步回调意味着我们运行代码的一部分,然后给它一个、在后面执行的回调函数。我们一定都遇到过异步回调像$.get()这样的ajax请求、setTimeout()、setInterval()、Promise等等。Node中全部都是关于异步函数执行的。所有的这些异步回调都是不立即运行,而是在某个时间之后才运行,因此他们不会像console.log(), 算数运算等这些同步函数一样被立即入栈。那么,他们到底去哪里了,又该怎么处理?


如果我们在JavaScript中看到一个类似于上面代码的网络请求:

  1. 执行请求函数,在onreadystatechange事件中传递匿名函数作为回调,以便在将来某个时候响应可用时执行。
  2. console会立即输出“Script call done!”。
  3. 将来的某个时间,响应到来并且我们的回调执行,输出他的响应body到console 调用者与响应的解耦允许JavaScript runtime 在等待异步操作完成及其回调触发时执行其他操作。

2这是浏览器自身的API发挥作用的地方,调用这些API处理诸如DOM事件、HTTP请求、StimeTimeUT等异步事件。(知道了这一点之后,在Angular 2 中,使用Zones来对这些API进行重新封装,以引起运行时更改检测,我现在可以了解一下它们是如何实现的)

现在,这些 WebAPI 本身不能将执行代码放到堆栈中,如果放的话,那么这些执行代码将随机出现在你们代码中。上面讨论的消息调用队列阐释了这一过程。3WebAPI中的任何一个在执行完后将回调推送到队列中。事件循环现在负责在队列中执行这些回调,并当堆栈为空时,将其推送到堆栈中。4事件循环的基本工作就是盯着栈和任务队列,当看到栈为空时,将队列中的第一项推到堆栈。在处理任何其他消息之前,会完全处理每个消息或回调。


在Web浏览器中,任何事件发生时都会添加消息,并且附加了事件侦听器。如果没有侦听器,则事件丢失。因此,单击一个带有单击事件处理程序的元素,将添加一个消息,而任何其他事件也是如此。这个回调函数的调用充当调用堆栈中的初始帧,并且由于JavaScript是单线程的,所以在堆栈上返回所有调用之前,将暂停进一步的消息轮询和处理。后续(同步)函数调用将新的调用帧添加到堆栈中。

在下一部分,我将展示上述过程的代码执行的可视化动画,进一步解释什么是不同类型的异步函数,如任务、微任务以及队列中谁的优先级高等。此外,类似零延迟的黑客用来执行某些功能。

希望,各位读者喜欢。您的宝贵意见,就是对我最大的支持。

注释

文章原文地址: Understanding Javascript Function Executions — Call Stack, Event Loop , Tasks & more

[译]深入理解JavaScript函数执行—调用栈,事件循环和任务等相关推荐

  1. 【译】理解Javascript函数执行—调用栈、事件循环、任务等

    原文作者:Gaurav Pandvia 原文链接:medium.com/@gaurav.pan- 文中部分链接可能需要梯子. 欢迎批评指正. 现如今,web开发者(我们更喜欢被叫做前端工程师)用一门脚 ...

  2. 「译」理解Javascript函数执行—调用栈、事件循环、任务等

    现如今,web开发者(我们更喜欢被叫做前端工程师)用一门脚本语言就能做任何事情,从提供浏览器中的交互,到开发电脑游戏.桌面工具.跨平台移动应用,甚至可以在服务端部署(如最流行的Node.js)来连结任 ...

  3. 深入理解javascript函数系列第二篇——函数参数

    前面的话 javascript函数的参数与大多数其他语言的函数的参数有所不同.函数不介意传递进来多少个参数,也不在乎传进来的参数是什么数据类型,甚至可以不传参数.本文是深入理解javascript函数 ...

  4. 2020-08-14 理解 javascript 函数中的 curry

    理解 javascript 函数中的 curry 函数柯里化: 所谓函数柯里化就是把一个函数的多个传参变成多个函数的单个传参. 函数的柯里化,是 Javascript 中函数式编程的一个重要概念.它返 ...

  5. JavaScript执行机制-node事件循环

    node环境下的事件循环机制 和浏览器有什么不同? 在node中,事件循环表现出来的状态和浏览器大致相同,但是node有一套自己的模型. node事件循环依靠libuv引擎,node选择chrome ...

  6. 【译】理解 JavaScript 中函数调用和 this

    原文 Understanding JavaScript Function Invocation and "this" github 的地址 欢迎 star! 前言 过去几年,我经常 ...

  7. 理解JavaScript的执行机制

    一直没有深入了解过JavaScript的事件执行机制,直到看到了这篇文章:<这一次,彻底弄懂JavaScript执行机制> 才发觉熟悉JavaScript的执行机制非常重要. 毕竟在跟进项 ...

  8. [译] 深入理解 JavaScript 事件循环(二)— task and microtask

    引言 microtask 这一名词是 JS 中比较新的概念,几乎所有人都是在学习 ES6 的 Promise 时才接触这一新概念,我也不例外.当我刚开始学习 Promise 的时候,对其中回调函数的执 ...

  9. Javascript函数执行、new机制以及继承

    JS函数执行 一个JavaScript函数fn,被执行有三种途径: fn() new fn() fn.call()或fn.apply() new机制以及继承 JavaScript中定义了一种对象,称之 ...

最新文章

  1. 这些实验好习惯科研小白一定要先养成!
  2. android list 比较,LinkedList 和 ArrayList 的区别
  3. SpringBoot_web开发-简介
  4. xshell使用xftp传输文件和使用pure-ftpd搭建ftp服务
  5. gradle ant_区分基于Ant目标的Gradle任务
  6. Java EE调度程序
  7. LeetCode每日打卡 - 汉明距离
  8. c语言char转cstring,CString、TCHAR*、char*转换 | 时刻需
  9. [原创]java WEB学习笔记71:Struts2 学习之路-- struts2常见的内建验证程序及注意点,短路验证,非字段验证,错误消息的重用...
  10. WebSocket 对象简介
  11. UIView动画---移动与变形
  12. 最优化算法 之 遗传算法代码实现及说明
  13. C语言实现24点游戏算法
  14. QT中实现二维码图片生成
  15. 安装爱剪辑计算机丢失,爱剪辑没保存的视频怎么恢复?
  16. 《帝国时代III黄金版》宣布
  17. 1790D Matryoshkas
  18. Javascript的设计模式之从设计到模式(其他设计模式)
  19. 国内奇葩小学数学题横扫推特,老外被难哭了
  20. ctf-crypto-7+1+0

热门文章

  1. 基于LINUX系统的音乐播放器
  2. 一张图带走一套操作 分享最新网络营销学习路线图-千锋
  3. 进化Unity Editor UX
  4. Wifi 的Log分析
  5. 人工大猩猩部队优化算法(GTO,ArtificialGorillaTroopsOptimizer)Matlab代码
  6. https+webservice
  7. 大一学生Web课程设计 红酒美食主题网页制作(HTML+CSS+JavaScript)
  8. 天体物理导论复习提纲
  9. windows 内核函数前缀解析
  10. 三种睡后收入,你也完全可以拥有