Node.js 中的 JavaScript
我们刚才分析的架构的一个重要结果是,我们在 Node.js 中使用的 JavaScript 与我们在浏览器中使用的 JavaScript 有些不同。
最明显的区别是,Node.js 中没有 DOM,也没有 window 或 document。另一方面,Node.js 可以访问底层操作系统提供的一系列服务,而这些服务是浏览器所不具备的。事实上,浏览器必须实施一系列安全措施,以确保底层系统不会被恶意网络应用程序破坏。浏览器为操作系统资源提供了一个更高层次的抽象,这使得它更容易控制和包含在其中运行的代码,但这也不可避免地限制了浏览器的功能。反过来,在 Node.js 中,我们几乎可以访问操作系统暴露的所有服务。
在本概述中,我们将介绍在 Node.js 中使用 JavaScript 时需要注意的一些关键事实。
自信地运行最新的 JavaScript
在浏览器中使用 JavaScript 的主要痛点之一是,我们的代码可能会在各种设备和浏览器上运行。处理不同的浏览器意味着要处理 JavaScript 运行时,而这些运行时可能会错过语言或网络平台的某些最新功能。幸运的是,如今可以通过使用转译器(transpilers)和多填充(polyfills)功能在一定程度上缓解这一问题。
不过,这也带来了一系列弊端,并不是所有东西都能进行多填充。在 Node.js 上开发应用程序时,所有这些不便都不适用。事实上,我们的 Node.js 应用程序很可能会运行在事先已知的系统和 Node.js 运行时上。这让我们可以针对特定的 JavaScript 和 Node.js 版本编写代码,绝对保证在生产环境中运行时不会出现意外情况,从而带来了巨大的不同。
这一因素,再加上 Node.js 搭载了最新版本的 V8,意味着我们可以放心使用最新 ECMAScript 规范(简称 ES,JavaScript 语言所依据的标准)的大部分功能,而无需任何额外的转译步骤。
但请记住,如果我们开发的是供第三方使用的库,我们仍然必须考虑到我们的代码可能会在不同版本的 Node.js 上运行。在这种情况下,一般的模式是以最新的长期支持 (LTS) 版本为目标,并在 package.json 中指定 engines 部分,这样,如果用户试图安装与其 Node.js 版本不兼容的软件包,软件包管理器就会发出警告。
您可以在 nodejsdp.link/node-releases 找到有关 Node.js 发布周期的更多信息。 此外,您还可以在 nodejsdp.link/package-engines 中找到 package.json 的 engines 部分的参考。 最后,您可以在 nodejsdp.link/node-green 上了解每个 Node.js 版本支持哪些 ES 功能。 |
模块系统
从一开始,Node.js 就配备了模块系统,即使 JavaScript 官方尚未支持任何形式的模块系统。最初的 Node.js 模块系统称为 CommonJS,它使用 require 关键字导入内置模块或设备文件系统中其他模块导出的函数、变量和类。
CommonJS 对整个 JavaScript 世界来说都是一场革命,因为它甚至在客户端世界也开始流行起来,它与模块捆绑程序(如 Webpack 或 Rollup)结合使用,可以生成便于浏览器执行的代码捆绑包。CommonJS 是 Node.js 的必要组件,可让开发人员创建与其他服务器端平台相同的大型、组织更好的应用程序。
如今,JavaScript 拥有所谓的 ES 模块语法(import 关键字可能更熟悉),Node.js 从中继承的只是语法,因为其底层实现与浏览器有些不同。事实上,浏览器主要处理远程模块,而 Node.js 至少目前只能处理本地文件系统中的模块。
我们将在下一章详细讨论模块。
完全访问操作系统服务
正如我们已经提到的,即使 Node.js 使用 JavaScript,它也不会在浏览器的边界内运行。 这使得 Node.js 能够绑定底层操作系统提供的所有主要服务。
例如,借助 fs 模块,我们可以访问文件系统上的任何文件(受到任何操作系统级别的权限),或者借助 net 和 dgram 模块,我们可以编写使用低级 TCP 或 UDP 套接字的应用程序。 我们可以创建 HTTP(S) 服务器(使用 http 和 https 模块)或使用 OpenSSL 的标准加密和哈希算法(使用 crypto 模块)。 我们还可以访问一些 V8 内部结构(v8 模块)或在不同的 V8 上下文中运行代码(使用 vm 模块)。
我们还可以运行其他进程(使用 child_process 模块)或使用进程全局变量检索我们自己的应用程序的进程信息。 特别是,从进程全局变量中,我们可以获得分配给进程的环境变量列表(使用 process.env)或在应用程序启动时传递给应用程序的命令行参数(使用 process.argv) 。
在本书中,你将有机会使用这里介绍的许多模块,但要获得完整的参考资料,你可以查看 nodejsdp.link/node-docs 上的 Node.js 官方文档。
运行本机代码
Node.js 提供的最强大功能之一无疑是创建可与本地代码绑定的用户模块。这为平台带来了巨大优势,因为它允许我们重用用 C/C++ 编写的现有或新组件。由于 N-API 接口的存在,Node.js 官方为实现本地模块提供了极大的支持。
但它有什么优势呢?首先,它允许我们不费吹灰之力地重用大量现有的开源库,最重要的是,它允许公司重用自己的 C/C++ 传统代码,而无需进行迁移。
另一个重要的考虑因素是,本地代码仍然是访问底层功能(如与硬件驱动程序或硬件端口(如 USB 或串口)通信)所必需的。事实上,由于 Node.js 能够链接到本地代码,它已在物联网(IoT)和自制机器人领域大受欢迎。
最后,尽管 V8 执行 JavaScript 的速度非常(非常)快,但与执行本地代码相比,它仍然要付出性能代价。在日常计算中,这很少成为问题,但对于 CPU 密集型应用(如需要处理和操作大量数据的应用)来说,将工作委托给本地代码会有很大意义。
我们还应该提到,如今大多数 JavaScript 虚拟机(VM)(以及 Node.js)都支持 WebAssembly (Wasm),这是一种低级指令格式,允许我们将 JavaScript 之外的语言(如 C++ 或 Rust)编译成 JavaScript 虚拟机可以 "理解" 的格式。这就带来了我们提到的许多优势,而无需直接与本地代码对接。
您可以在该项目的官方网站 nodejsdp.link/webassembly 上了解有关 Wasm 的更多信息。 |