[译] 你不知道的 Node

在今年的 Forward.js 大会(一个 JavaScript 峰会),我进行了一场主题为“你不知道的 Node” 的演讲,在那场演讲中,我问了现场观众一系列关于 Node.js 运行时的问题,然而大部分搞技术的听众都不能所有回答得上。前端

我当时并无真的计算过,直到演讲完了才有一些勇敢的人过来跟我坦白说他们不会。node

这个问题正是让我发表演讲的缘由,我并不认为咱们教授 Node 的方式是对的。大多数关于 Nodejs 的教材内容主要集中在 Node 包和 Node 运行时以外的地方,大多数这些包都在 Node 运行时封装好了模块(例如 httpstream),问题多是藏在运行时里面,然而你不懂 Node 运行时的话,你就麻烦了。react

问题:大多数关于 Nodejs 的教材内容主要集中在 Node 包和 Node 运行时以外的地方。android

我挑选了几个问题并组织了一些答案来写成这篇文章,答案就在问题的下面,建议尝试先本身回答。ios

若是你发现了错误或误导性的回答,请跟我联系。git

问题 #1:什么是调用栈?它是 V8 的一部分吗?

调用栈百分之百就是 V8 的一部分,它是 V8 用来追踪方法调用的数据结构。每一次咱们调用一个方法,V8 在调用栈中放置一个该方法的引用,而且 V8 对每一个其余方法的嵌套调用也这样操做,同时也包括那些自身递归调用的方法。github

Screenshot captured from my Pluralsight course — Advanced Node.js
Screenshot captured from my Pluralsight course — Advanced Node.js

当方法的嵌套调用结束时,V8 会逐个地将方法从栈中 pop 出来,并在它的位置使用方法的返回值。后端

为何这对于理解 Node 是如此关键?由于在每一个 Node 进程中你只有一个调用栈。若是你令调用栈处于忙碌,你整个的 Node 进程也将变得忙碌。牢记这一点!浏览器

问题 #2:什么是事件循环?它是 V8 的一部分吗?

你以为事件循环在这张图的哪一个部分?bash

Screenshot captured from my Pluralsight course — Advanced Node.js
Screenshot captured from my Pluralsight course — Advanced Node.js

答案是 libuv 。事件循环不是 V8 的一部分!

事件循环是操控外部事件并将它们转换为回调调用的实体,它是从事件队列中取出事件并将事件的回调函数推动调用栈的一个循环。而且该循环过程当中分为多个独立的阶段。

若是这是你第一次据说事件循环,这些概念对你可能帮助不大。事件循环是一副很大的轮廓图的其中一部分:

Screenshot captured from my Pluralsight course — Advanced Node.js
Screenshot captured from my Pluralsight course — Advanced Node.js

你须要先理解这幅轮廓图再理解事件循环,你须要先理解 V8 在这里面饰演的角色、理解 Node APIs 并知道事件是怎样进入队列并被 V8 处理的。

Node APIs 是像 setTimeoutfs.readFile的一些方法,它们不是 JavaScript 自己的一部分,它们就是 Node 提供的方法。

事件循环在这张图片的中间(一个更复杂的版本,真的)饰演一个组织者的角色。当 V8 调用栈为空的时候,事件循环能够决定接下来执行什么。

问题 #3:当调用栈和事件循环队列都为空时,Node 会作什么?

Node 会直接退出。

当你执行一个 Node 程序时,Node 会自动地开始事件循环,当没有事件处理时而且没有其余任务时,Node 则会退出进程。

为了保持一个 Node 进程持续运行,你须要把一些任务放入事件队列中。例如,当你建立一个计时器或一个 HTTP 服务器时,你基本上就是在告诉事件循环要保持并检测这些任务持续执行。

问题 #4:除了 V8 和 Libuv,Node 还有哪些外部依赖?

如下是一个 Node 进程可使用的全部外部的库:

  • http-parser
  • c-ares
  • OpenSSL
  • zlib

对 Node 自己来讲,上面这些库都是外部的,这些库都有本身的源代码、许可证,Node 只是使用它们而已。

你想记住它们是由于你想知道你的程序执行到哪里了,若是你在作一些数据压缩的工做,有多是在 zlib 这个库遇到问题,Node 是无辜的。

问题 #5:不用 V8 有可能运行一个 Node 进程吗?

这多是一个奇技淫巧的问题。你确定是须要一个虚拟机去执行 Node 进程,但 V8 并非惟一的虚拟机,你还可使用 Chakra。

查看这个 Github 仓库来跟踪 node-chakra 项目的进度:

问题 #6:module.exports 和 exports 二者的区别?

你可使用 module.exports 导出模块的 API,你也可使用 exports,但有个值得注意的地方:

module.exports.g = ...  // Ok

exports.g = ...         // Ok

module.exports = ...    // Ok

exports = ...           // Not Ok复制代码

为何?

exports 只是一个对 module.exports 的引用或别名,当你修改 exports 时你实际上是在无心中试图修改 module.exports,但修改对官方 API (即 module.exports)不会产生影响,你只是在模块做用域中获得一个局部变量。

问题 #7:为何顶层变量不是全局变量?

若是你在 module1 定义了一个顶层变量 g

// module1.js

var g = 42;复制代码

而你在 module2 依赖 module1并试图访问这个变量 g,你会获得错误 g is not defined

为何? 若是你在浏览器执行相同的操做,你能够在全部脚本中访问顶层定义的变量。

每一个 Node 文件在背后都有本身的 IIFE(当即调用函数表达式),全部在一个 Node 文件中声明的变量都被限制在这个 IIFE 的做用域中。

相关问题: 在一个 Node 文件中只有下面这一行代码,执行它会输出什么:

// script.js</pre>

console.log(arguments);复制代码

你会看到一些参数!

为何?

由于 Node 执行的是一个函数。Node 将你的代码包裹在一个函数中,这个函数明确地定义了你上面看到的那 5 个参数。

问题 #8:exportsrequire、和 module三个对象在每一个文件中都是全局可用的,但他们在每一个文件中又有区别,为何呢?

当你须要使用 require 对象时,你只是像使用全局变量那样直接使用它,然而,若是你在 2 个不一样的文件中比较 require 对象的区别,你会发现 2 个不一样的对象,怎么回事?

仍是由于同样的缘由 IIFE(当即调用函数表达式):

正如你所见,IIFF 将如下 5 个参数传递到你的代码中:exports, require, module, __filename, and __dirname

当你在 Node 中使用这 5 个变量的时候彷佛是在使用全局变量,但它们只是函数参数。

问题 #9: Node 中的循环依赖是什么?

若是你有一个 module1 依赖于 module2,而 module2 又反过来依赖于 module1,这将发生什么?一个错误?

// module1

require('./module2');

// module2

require('./module1');复制代码

放心,不会报错,Node 容许这样作。

因此,module1 依赖于 module2,但由于 module2 又依赖于 module1,然而 module1 此时还没就绪,module1 只会获得 module2 的不完整版本。

系统已经发出警告了。

问题 #10:何时适合使用文件系统的同步方法(像 readFileSync)?

每一个 Node 中的 fs 方法都有一个同步版本,为何你要使用一个同步方法而不是一个异步方法?

有时使用同步方法挺好的,举个例子,能够在服务器还在一直加载的时候,将同步方法用到任何初始化工做中。一般状况下,在初始化工做完成以后,你接下来的工做是根据得到的数据继续进行做业而不是引入回调级别。使用同步方法是能够接受的,只要你使用的同步方法是一次性的。

然而,若是你在一个像是 HTTP 服务器的 on-request 回调函数里使用同步方法,那就真的是 100% 错误!别那样作。

我但愿你能答上一部分或者全部的问题,如下是我写得比较深刻 Node.js 细节的文章:


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOSReact前端后端产品设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索