了解 Node.js

简介内容

什么是 Node.js

Node.js(简称 Node)是开源服务器端 JavaScript 运行时环境

  • Node.js 是名为 V8 的 JavaScript 引擎的包装器
  • Node.js 还包含在服务器上运行的应用程序可能需要的许多 V8 优化

Node.js 使用许多新式服务和框架所使用的语言和技术,可以使用 Node.js 生成以下类型的应用程序:

  • HTTP Web 服务器
  • 微服务或无服务器 API 后端
  • 用于数据库访问和查询的驱动程序
  • 交互式命令行接口
  • 桌面应用程序
  • 实时物联网 (IoT) 客户端和服务器库
  • 适用于桌面应用程序的插件
  • 用于文件处理或网络访问的 Shell 脚本
  • 机器学习库和模型
  • ... ...

Node.js 的工作原理

Node.js 是基于事件驱动的 I/O 范例的单线程、非阻止的运行时

  • Node.js 可以在浏览器以外的主机上解释和运行 JavaScript 代码,所以运行时可以直接访问操作系统 I/O文件系统网络

  • Node.js 基于单线程事件循环

    • 此体系结构模型可高效地处理并发操作
    • 单线程是指 JavaScript 只有一个调用堆栈,一次只能执行一项任务
    • 事件循环运行代码,收集和处理事件,并在事件队列中运行下一个子任务

此上下文中

  • 线程是操作系统可以独立管理的单个编程指令序列
  • 并发是指事件循环在完成其他工作之后执行 JavaScript 回调函数的能力

在 Node.js 中,I/O 操作被视为阻止操作(阻止操作会阻止所有后续任务,直到该操作完成,然后才能继续下一个操作)

事件循环的主要阶段包括:

  • 计时器:处理由 setTimeout(), setInterval() 计划的回调函数
  • 回调:运行挂起的回调函数
  • 轮询:检索传入的 I/O 事件并运行与 I/O 相关的回调函数
  • 检查:允许完成轮询阶段后立即运行回调函数
  • 关闭回调:关闭事件和回调

总而言之,事件循环运行为事件注册的 JavaScript 回调,还负责实现非阻止异步请求

异步编程

  • 为支持功能强大的基于事件的编程模型,Node.js 提供了一组内置的非阻止 I/O API 来处理文件系统和数据库操作等常见任务。 这些 API 由 libuv 库提供
  • 当触发鼠标或键盘事件,或者从远程终结点接收到 XMLHttpRequest (XHR) 响应时,该非阻止 I/O 的工作方式与浏览器通知代码时的方式相同

安装和使用 Node.js

以下介绍了一些最常见的方式:

  • 通过可执行文件进行安装 https://nodejs.org/en/download/
  • 通过 Brew 安装,适用于 Linux 和 macOS 的常用包管理器
  • 通过 nvm 安装,有助于管理安装

不同的源代码版本:LTS 是长期支持的缩写,另一个表示源代码处于积极开发阶段

验证安装

node --version

npm 基本使用

Node.js 环境还提供了一个 npm 注册表(可用于共享你自己的 Node.js 库)

  • 社区已为 Node.js 构建了逾一百万个模块和库,并将这些模块和库发布到了节点包管理器 npm
  • 丰富多样的生态,模块和库可在 Node.js 上运行的应用程序包括命令行工具、框架、Web 服务器等
  • 为了提高速度和可靠性,你和你的团队可能不会自行编写所有代码

package.json

package.json 文件是 Node.js 项目的清单文件

{
    "name": "my project",       // 元信息
    "version": "1.0.0",         // 元信息
    "author": "",               // 元信息
    "description": "",          // 元信息
    "keywords": [],             // 元信息
    "license": "ISC",           // 元信息
    "main": "script.js",        // 元信息

    "dependencies": {},         // 依赖项
    "devDependencies": {},      // 依赖项

    // 脚本
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    
}
  • 此文件不是你手动创作的内容,可以在命令行使用 npm init 自动生成初始 package.json 文件

    • init: 此命令启动一个向导,该向导将提示你提供有关项目的名称、版本、说明、入口点、测试命令、Git 存储库、关键字、作者和许可证的信息
    • npm init -y: 使用了 -y 选项,系统会自动为提示你输入的所有值分配默认值
  • package.json 文件中所有可能的属性分组:

    • 元信息:此组中的属性定义了有关项目的元信息(项目名称、说明、作者、关键字等)
    • 依赖项:有 dependencies 和 devDependencies 这两个属性,用于说明所使用的库
    • 脚本:可以列出项目任务的脚本,例如开始、生成、测试和 lint
  • 用于管理项目的脚本

    Node.js 运行时可识别此需求,并提供有关如何命名脚本的指导,其思路是确保所有 Node.js 项目都使用一致的脚本名称

    • start: 使用条目文件作为参数调用 node

    • build: 说明如何生成项目,生成过程应生成一些可交付的内容

    • test: 运行项目的测试,如果使用第三方测试库,则该命令应调用库的可执行文件

    • lint: 调用类似 ESLint 的 Linter 程序,具有一致的代码可以极大地提高其可读性,进而加速功能的开发和代码的添加

      • Lint 分析可查找代码中不一致的内容
      • Linter 通常还提供一种方法来更正不一致
  • package.json 中 scripts 属性示例

    "scripts" : {
        "start" : "node ./dist/index.js",
        "test": "jest",
        "build": "tsc",
        "lint": "eslint"
    }
    
    • 使用在命令行 npm run <action> 来调用操作,比如 npm run build
    • start 和 test 操作特殊在于你可以在命令中 run

演示

  • 创建一个文件夹或目录 PackageTest,名字随意

  • 创建下面内容

    -| src/
    ---| index.js
    
  • index.js 写入

    console.log('Welcome to this application');
    
  • 命令行在目录 PackageTest 中运行 npm init -y 创建初始 package.json

  • 在生成 package.json 中写入

    {
        "name": "PackageTest",                                      // 项目名称随意
        "version": "1.0.0",
        "description": "测试 Node.js 中 npm 初始化 package.json",
        "main": "script.js",
        "dependencies": {},
        "devDependencies": {},
        "scripts": {
            "start": "node ./src/index.js",                         // 使用 index.js
            "test": "echo \"Error: no test specified\" && exit 1"
        },
        "keywords": [],
        "author": "sha_dow",
        "license": "ISC"
    }
    
  • 命令行在目录 PackageTest 中运行 npm start

    Welcome to this application
    

在 Node.js 项目中添加包

为什么需要包

  • 获取更好的代码
  • 节省时间
  • 不用自己维护

评估包是否需要

  • 大小:依赖项的数量可能会造成很大的占用量
  • 许可证:可能会让你陷入法律困境
  • 维护性:如果你的包依赖于已弃用或长时间未更新的依赖项,这可能是个问题

安装包的命令 npm install <name of package>

  • 在运行 install 命令时,命令行工具连接到全局注册表并提取代码,将其放在项目的 node_modules 文件中
  • 使用 -g 选项,会进行全局安装
  • 如果要查找一个包,可以去 http://npmjs.org

npm 命令可以协助你完成管理依赖项运行脚本配置环境创作和发布包,推荐 npm --help (合格的开发者需要学会如何 help)

依赖项属于以下两个类别之一,使用 npm view <package name> 可以查看依赖

  • 生产依赖项:生产依赖项是应用程序投入生产时需要运行的依赖项

    必须在应用程序中内置生产依赖项,以便在应用程序运行时该功能可用。 示例包括一个可用于生成 Web 应用程序的 Web 框架。

  • 开发依赖项:开发依赖项是仅在开发应用程序时需要的依赖项

    可将这些依赖项视为修建建筑物时使用的脚手架。 完成修建后,你便不再需要它们了。 这些依赖项的示例包括测试库、Lint 分析工具或捆绑工具。 这些依赖项是确保应用程序正常运行的重要部分,但你不需要随应用程序一起提供。

处理依赖

  • 这种分离不同类型依赖项的方式还会内置于 npm 命令行工具中,如果在安装依赖项时指定了 --production 选项,则只会安装生产 dependencies
  • 将依赖项的名称及其版本放置在一个名为 dependenciesdevDependencies 的部分

在安装包之后,可以输入 npm list 命令查看包

  • npm list --depth=<depth> 其中 depth 表示查看深度,防止过多的内容
  • npm outdated 命令列出了已过时的包
  • npm update <optional package name> 命令以更新已安装的包

每次更新或安装包时,都会在安装完成后获得日志响应(该响应将告诉你所安装的版本,以及是否有任何漏洞)

  • 要修复问题并应用更新,可以按日志响应所示运行 npm audit 命令
  • npm audit fix 命令尝试修复此问题,它尝试升级到不存在此问题的次要版本
  • npm audit fix --force 命令来修复问题,此操作涉及中断性更改(即包的主版本)

它们非常常见,GitHub 已实现了一个函数,用于扫描存储库并自动创建 PR,在发现漏洞时建议你升级到更安全的版本

使用 npx: 依赖项会直接下载到 Node.js 进程中,并在运行命令后删除(当你很少需要运行命令时,该工具非常适合)

清理依赖项

可以通过两种方法清理不再需要的依赖项

  • 卸载:要卸载包,请运行 npm uninstall <name of dependency>

    此命令不仅将从清单文件中删除包,还会从 node_modules 文件夹中删除包

  • 删除:还可以运行 npm prune 命令

    通过运行此命令,可删除 node_modules 文件夹中未在清单文件中作为依赖项列出的所有依赖项

    要使用此命令删除未使用的依赖项,请先从清单文件的 dependencies 或 devDependencies 部分删除条目

使用语义化版本控制

语义化版本控制是指如何表达你或其他开发人员向库引入的更改类型

语义化版本控制的工作原理是确保包具有版本号(比如 1.2.3),并且该版本号划分为以下部分:

  • 主版本:最左边的数字

    此数字发生更改意味着代码可能出现中断性变更,可能需要重写部分代码

  • 次要版本:中间的数字

    此数字发生更改意味着添加了新功能,你的代码仍可正常工作,接受更新通常是安全的

  • 修补程序版本:最右边的数字

    此数字发生更改意味着应用了一个更改,修复了代码中应正常工作的内容,接受更新应是安全的

作为 Node.js 开发人员,你可以向 Node.js 传达所需的更新行为,从风险角度考虑更新:

  • 主版本:我很乐意在最新主版本发布后立即更新到最新主版本,我接受可能需要更改我的代码这一事实
  • 次要版本:我能接受添加新功能,我不能接受代码中断
  • 修补程序版本:我唯一能接受的更新是 bug 修复

为更新配置 package.json

  • * 更新到最高主版本
  • ^ 仅更新到次要版本
  • ~ 更新到最新修补程序版本
  • 或者使用 x 模糊匹配对于版本号位置,比如 ^1.0.0 等价 1.x.0

示例

"devDependencies": {
    "jest": "^29.4.2"
}

package-lock.json

除了 package.json 清单文件外,你还具有 package-lock.json 文件

  • 当你执行一些修改 node_modules 目录的操作,或执行更改 package-lock.json 文件中的依赖项的任何操作时,将生成此文件
  • 应将 package-lock.json 文件提交到存储库,将此文件提交到存储库的一个原因是它可以保证完全安装
  • package-lock.json 文件还提供其他功能,它便于查看提交之间的更改,并有助于优化安装过程

了解此过程以及哪个文件确定何时进行安装很重要,其工作方式如下所述

  • 如果 package.json 和 package-lock.json 文件就语义规则级别达成一致,则不会发生冲突

    例如,如果模式在 package.json 文件中指明 1.x,而 package-lock.json 文件指定安装版本 1.4,则将安装版本 1.4

  • 如果 package.json 文件指定了类似 1.8.x 的模式,则不会实现 package-lock.json 文件中的说明,将安装次要版本 1.8.0 或更高版本,或更高修补程序版本(如果有)

查找和更新过时的包