原文:You don’t know Nodenode
译者:neal1991git
welcome to star my articles-translator , providing you advanced articles translation. Any suggestion, please issue or contact megithub
LICENSE: MIT浏览器
在今年 Forward.js (一个 JavaScript)会议中,我作了主题为“你并不知道 Node”的演讲。在这个演讲中,我向观众提出一些关于 Node.js 运行时的具备挑战性的问题,大多数技术相关观众没法回答其中的大部分问题。服务器
我并无进行实际的统计,可是在那个房间感受是这个样子的。而且有一些勇敢的观众在演讲以后向我认可了这一事实。数据结构
这也就是我作这次演讲的缘由。我不认为咱们以正确的方式教学 Node!大多数的 Nodejs相关的教学概念集中于Node 包却不是它的运行时。大多数的包都将 Node 运行时包裹在模块中(好比 http 或者 stream)。当你遇到问题的时候,这些问题可能存在于运行时之中,若是你不知道Node 运行的话, 你就陷入麻烦了。app
这个问题:多数的 Nodejs相关的教学概念集中于Node 包却不是它的运行时异步
我为这篇文章选择了一些问题而且作出回答。它们将以标题的形式在下面呈现。首先尝试在你本身心中作出回答!函数
若是你发现错误或者具备误导性的回答,请让我知道。ui
调用栈固然是 V8 的一部分。它是 V8 用来追踪函数调用的一种数据结构。每次咱们调用一个函数的时候,V8都会将向函数调用栈中压入一个对于函数的引用,它对于其它函数的内嵌函数也会这样作。这也包括递归调用函数的函数。
当函数中的内嵌函数到达末端的时候,V8 就会每次弹出一个函数而且在它的位置使用返回值。
为何理解 Node 是重要的?由于你在每一个 Node 进程中只能获取一个调用栈。若是你让这个调用栈保持忙碌,那么你的整个 Node 进程也会是忙碌的。记住这一点。
你认为事件循环是在这张图的什么地方?
事件循环是由 libbuv 库提供,它不是 V8 的一部分。
事件循环处理外部事件而且将它们转换成回调调用。它是一个从事件队列中跳去事件的循环而且将它们的回调压入到调用栈中。它也是一个多相回调。
若是这是你第一次据说事件循环,这些概念将可能不会那么有用。这个事件循环是一张更大的图的一部分。
你须要理解这张更大的图从而理解事件循环。你须要理解 V8 的角色,知道 Node 的 API,而且知道事件如何进入队列从而被 V8 所执行。
Node 的 API 就是一些函数,好比 setTimeout
或者 fs.readFile
。这些并非 JavaScript 中的一部分。它们是由 Node 提供的函数。
事件循环位于这张图(真的是一个更复杂的版本)的中间位置,就好像是一个组织者。当 V8 调用栈为空的时候,事件循环能够决定下一步执行哪个。
很简单,它会退出。
当你运行一个Node程序的时候,Node 将会自动开始事件循环而且当事件循环变为 idle 的时候而且没有其它的事情须要作的时候,这个进程将会退出。
为了保持 Node进行运行,你须要在事件队列的某个地方放一些东西。好比,当你启动一个计时器或者一个 HTTP 服务的时候,你基本上就是告诉事件循环保持运行而且检查这些事件。
下面的是 Node 进行须要的全部的单独库:
它们对于 Node 来讲都是外部依赖。它们具备它们本身的源代码。它们具备它们本身的证书。Node 只是使用它们。
你但愿记住由于你想知道你的程序在什么地方运行。若是你在处理数据压缩,你可能遇到一些 zlib 库使用的一些困难。你可能在解决一个 zlib 的 bug。不要把全部的事都怪罪于 Node。
这多是一个棘手的问题。你须要一个 VM 来运行 Node 进程,可是 V8 并非你惟一能够试用的 VM。你可使用 Chakra。
获取 Github仓库来跟追踪 node-chakra 的进程:https://github.com/nodejs/nod...
你能够老是试用 module.exports
来导出你模块的 API。你也能够exports
除了一种状况:
module.exports.g = ... // Ok exports.g = ... // Ok module.exports = ... // Ok exports = ... // Not Ok
为何?
exports
只是一个对于 module.exports
的引用或者别名。当你改变 exports
的时候,你是在改变那个引用而不是改变官方的 API(module.exports
)。你将只会得到一个模块做用域内的局部变量。
若是你有一个 module1
定义了一个顶级变量 g
:
// module1.js var g = 42;
而且你有一个 module2
引入了 module1
而且尝试访问变量 g
,你将会发现 g 是未定义的
。
为何?在浏览器中你若是作一样的操做,你可以在全部的变量在定义以后被引入就能够访问顶级变量。
每个 Node 文件都会在背后获取它本身的当即执行函数表达式(IIFE)。全部在这个 Node 文件里面定义的变量都是在这个 IIFE 做用域内。
相关问题:运行下面仅仅包含一行代码的 Node 文件的输出回事什么:
// script.js console.log(arguments);
你将会看到一些参数!
为何?
由于 Node 执行的是一个函数。 Node 将你的代码使用函数来包装而且这个函数准肯定义了上面你看到的5个参数。
exports
,require
和module
都是在每一个文件中全局可用,但每一个文件都不一样。 这是如何实现的?当你须要使用 require
对象的时候,你就能够直接使用它就好像是一个全局变量。然而,若是当你在不一样的两个文件中检测 require
,你将会看到两个不一样的对象。这是如何实现的?
都是由于相同的神奇的 IIFE:
正如你所见,神奇的 IIFE 将你的代码传递到5个参数之中:exports
,require
,modue
,__filename
以及__dirname
。
当你在 Node 试用这5个参数的时候,它们看起来是全局的,可是事实上它们仅仅是函数的参数。
若是你有一个 module1
引入 module2
而且一样 module2
也引入了 module1
,那么将会发生什么?一个错误?
// module1 require('./module2'); // module2 require('./module1');
你将不会获得一个错误。Node 容许这种状况。
那么在 module1
引入 module2
的时候,可是由于 module2
须要 module1
而且 module1
还没有完成,module1
将会仅仅获取 module2
的一个部分版本。
你将被警告。
Node 中的每个 fs
方法都有一个同步的版本。为何你要使用一个同步方法而不是一个异步方法呢?
有时候使用同步方法是不错的。好比,在初始化步骤中服务器依然在加载的状况下使用同步方法。大多数状况是初始化步骤以后的全部事取决与在初始化步骤中获取的数据。在不引入回调的层级,使用同步方法是能够接受的,只要你使用同步方法是一次性的事情。
然而,若是你在一个处理程序中试用同步方法,好比 HTTP 服务器请求回调,那将会很明显 100% 报错。不要那样作。
我但愿你可以回答一些或者所有这些具备挑战性的问题。我将会给一些除了 Node.js 基本概念之外的文章。
https://medium.freecodecamp.o...
https://medium.freecodecamp.o...
https://medium.freecodecamp.o...
https://medium.freecodecamp.o...
https://medium.freecodecamp.o...
https://medium.freecodecamp.o...