NodeJS

Stone大约 16 分钟

NodeJS

Node.jsopen in new window 是一个跨平台 JavaScriptopen in new window 运行环境,是一个基于 Chrome 的 V8 引擎的 JavaScript 运行环境,使开发者可以搭建服务器端的 JavaScript 应用程序。

  • 非阻塞 I/O:Node.js 采用非阻塞 I/O 模型,使其能够处理大量的并发连接,而不会像传统的服务器那样为每个连接创建一个新的线程。这使得 Node.js 在处理高并发场景时表现优异。
  • 事件驱动:Node.js 使用事件驱动编程模型,这意味着它会在特定的事件发生时执行相应的回调函数。这种模型使得代码更加模块化,易于理解和维护。
  • 单线程:虽然 Node.js 是单线程的,但由于其非阻塞 I/O 特性,它仍然能够高效地处理大量并发请求。此外,Node.js 的 V8 引擎还使用了多种优化技术,以提高其性能。
  • 跨平台:Node.js 可以在多种操作系统上运行,包括 Windows、Linux 和 macOS 等。
  • 丰富的生态系统:Node.js 拥有一个庞大的生态系统,包括大量的第三方模块和工具,这些都可以通过 npm 进行安装和管理。这使得开发者能够快速地构建功能丰富的应用程序。

虽然可以使用 Node.js 编写后端程序,但在实际工作中,Node.js 更多用于前端工程化,以集成各种开发中使用的工具和技术。

安装

官方网站open in new window下载 Node.js,然后进行安装。

需要注意:

  • 安装在非中文路径下。
  • 无需勾选自动安装其他配套软件。

安装完成后,打开命令提示符(cmd),输入命令 node -vnpm -v,如果能够显示出版本号,则说明安装成功。

模块化

Node.js 的模块化系统是其核心特性之一,它允许开发者将代码分割成不同的文件或模块,每个模块都有自己独立的作用域,避免了全局命名空间的污染。同时,模块之间可以通过特定的接口进行通信和协作,提高了代码的可维护性和可重用性。

Node.js 的模块主要分为以下几类:

  • 内置模块:这是Node.js 官方提供的模块,也称为核心模块。在安装 Node.js 时,这些模块就已经包含在内,无需额外安装。常见的内置模块包括 fs(用于文件操作)、http(用于创建 HTTP 服务器和客户端)、path(用于处理文件路径)、os(提供与操作系统相关的功能)等。这些模块为开发者提供了丰富的API,用于处理文件、网络请求、操作系统交互等任务。
  • 自定义模块:这是程序员自己编写的模块。这些模块通常根据项目的特定需求来创建,用于封装可重用的代码和功能。自定义模块可以使得代码更加模块化、易于维护和管理。
  • 第三方模块:这是由其他程序员编写并发布的模块。Node.js 的生态系统中有一个庞大的第三方模块库,涵盖了各种功能,如数据库连接、模板引擎、测试框架等。开发者可以通过 npm(Node Package Manager)来安装和管理这些第三方模块,从而加速开发进程并提高代码质量。

在 JavaScript 中,有多种方式可以实现模块化,包括早期的 CommonJS 规范和现在广泛使用的 ECMAScript 模块(ESM)规范。

  • CommonJS 规范:一般应用在 Node.js 项目环境中
  • ECMAScript 规范:一般应用在前端工程化项目中

CommonJS 规范

CommonJS 是一种在 Node.js 环境中广泛使用的模块化规范。CommonJS 是同步加载模块的,这意味着它会阻塞代码的执行,直到模块加载完成。由于这种同步特性,CommonJS 更适合在服务器端使用。

在 CommonJS 规范中,每个文件被视为一个模块,拥有自己独立的作用域、变量以及方法等,对其他的模块都不可见。每个模块内部,module 变量代表当前模块,它是一个对象,其 exports 属性是对外的接口。加载某个模块,实际上是加载该模块的 module.exports 属性。require 方法用于加载模块,使模块能够按照规范的方式进行组织和调用。即通过 module.exports 暴露模块内部属性和方法给外部,其他模块通过 require 函数来引入并使用这个模块暴露出的内容。

语法:

  • 导出:module.exports = {}
  • 导入:require('模块名或路径')
    • 内置模块直接写模块名(例如:fspathhttp),第三方模块直接写安装时指定的包名
    • 自定义模块写模块文件路径(例如:./utils.js

注意:

  • 使用 require() 方法加载其他模块时,会执行被加载模块中的代码。
  • 模块在第一次加载后会被缓存,这也意味着多次调用 require() 方法不会导致模块的代码被执行多次。
  • 内置模块是由 Node.js 官方提供的模块,加载优先级最高,被覆盖同名的自定义模块。
  • 使用 require() 方法加载自定义模块时,必须指定以 ./ 或者 ../ 开头的路径标识符。如果没有指定以 ./ 或者 ../ 开头的路径标识符,则会被当成内置模块或第三方模块进行加载。
  • 使用 require() 方法加载自定义模块时,如果省略了文件的扩展名,则 Node.js 会按顺序分别尝试加载以下文件:
    • 安装确切的文件名进行加载
    • 补全 .js 扩展名进行加载
    • 补全 .json 扩展名进行加载
    • 补全 .node 扩展名进行加载
    • 加载失败,终端报错
  • 如果传递给 require() 的模块标识符不是一个内置模块,也没有以 ./ 或者 ../ 开头,则 Node.js 会从当前模块的父目录开始,尝试从 node_modules 目录中加载第三方模块,如果没有找到,则移动到上一层父目录的 node_modules 目录查找,直到文件系统的根目录。
  • 当把目录作为模块标识符,传递给 require() 进行加载时,加载方式如下:
    • 被加载的目录下查找 package.json 文件中的 main 属性,作为 require() 加载的入口。
    • 如果目录没有 package.json 文件,或者没有 main 属性,则加载目录下的 index.js 文件。

例子:创建 mathUtils.js 的模块,提供数学计算的功能

// mathUtils.js  
function add(a, b) {  
  return a + b;  
}  
  
function subtract(a, b) {  
  return a - b;  
}  
  
module.exports = {  
  add: add,  
  subtract: subtract  
};

在另一个文件 app.js 中,可以引入并使用这个 mathUtils.js 模块:

// app.js  
const mathUtils = require('./mathUtils');  
  
const sum = mathUtils.add(5, 3);  
const difference = mathUtils.subtract(sum, 2);  
  
console.log(`Sum: ${sum}`);  
console.log(`Difference: ${difference}`);

终端执行:

$ node app.js
Sum: 8
Difference: 6

在上面的例子中,mathUtils.js 通过 module.exports 导出了两个函数 addsubtract。然后,在 app.js 中,使用 require 函数引入了 mathUtils 模块,并将其赋值给 mathUtils 变量。之后,就可以通过 mathUtils 变量来调用 addsubtract 函数了。

ECMAScript 规范

除了使用 CommonJS 规范(requiremodule.exports)进行模块化外,Node.js 还支持 ECMAScript 模块(ESM),这是 JavaScript 的官方标准模块系统。ESM 是异步加载模块的,这意味着它不会阻塞代码的执行,因此更适合在浏览器环境中使用。使用 ESM,可以通过 importexport 关键字来导入和导出模块。但请注意,为了使用 ESM,需要在 package.json 文件中设置 "type": "module" 或者使用 .mjs 文件扩展名。

默认导出导入

语法:

  • 导出:export default {}
  • 导入:import 变量名 from '模块名或路径'

注意:

  • 默认导出可以是任何 JavaScript 对象,但每个模块只能有一个默认导出。
  • 导入默认导出时,不需要使用花括号。

例子:创建 mathUtils.js 的模块,提供数学计算的功能,并默认导出

// mathUtils.js  
function add(a, b) {  
  return a + b;  
}  
  
function subtract(a, b) {  
  return a - b;  
}  
  
export default {
  add: add,  
  subtract: subtract  
};

在另一个文件 app.js 中,使用 import 导入 mathUtils 模块:

// app.js  
import mathUtils from "./mathUtils.js";
  
const sum = mathUtils.add(5, 3);  
const difference = mathUtils.subtract(sum, 2);  
  
console.log(`Sum: ${sum}`);  
console.log(`Difference: ${difference}`);

创建 package.json 文件并设置 "type": "module"

{
  "type": "module"
}

终端执行:

$ node app.js
Sum: 8
Difference: 6

在上面的例子中,mathUtils.js 通过 export default 导出了两个函数 addsubtract。然后,在 app.js 中,使用 import 函数引入了 mathUtils 模块,并将其赋值给 mathUtils 变量。之后,就可以通过 mathUtils 变量来调用 addsubtract 函数了。

命名导出导入

可以使用 export 关键字来导出任何变量、函数、类或对象字面量,并为它们指定一个名称,这使得其他模块可以按需导入所需的特定功能或数据。

语法:

  • 导出:export 定义语句
  • 导入:import {导出文件同名变量} from '模块名或路径'

注意:

  • 命名导出可以导出多个值,每个值都有自己的名称。
  • 命名导入需要使用花括号 {} 来包围想要导入的导出名称。
  • 如果一个模块同时有默认导出和命名导出,可以在导入时同时处理它们。
  • 导入时,导出名称必须与导出时使用的名称完全匹配(包括大小写)

例子:创建 mathUtils.js 的模块,提供数学计算的功能,并命名导出

// mathUtils.js  
export function add(a, b) {  
  return a + b;  
}  
  
export function subtract(a, b) {  
  return a - b;  
}  
  
export default {  
  add: add,  
  subtract: subtract  
};

在另一个文件 app.js 中,使用 import 导入模块:

// app.js  
import {add, subtract} from "./mathUtils.js";
  
const sum = add(5, 3);  
const difference = subtract(sum, 2);  
  
console.log(`Sum: ${sum}`);  
console.log(`Difference: ${difference}`);

终端执行:

$ node app.js
Sum: 8
Difference: 6

在Node.js中,“包”(package)是一个重要的概念,它指的是一个可以发布和重用的代码单元。包通常包含一系列的 JavaScript 文件、模块、库、资源文件(如图片、JSON 数据等)以及相关的元数据(如包的名称、版本、描述等)。这些包被组织成一个文件结构,通常称为 “包结构” 或 “项目结构”。

Node.js 的包管理主要依赖于 npm(Node Package Manager),这是一个开源的 JavaScript 包管理工具。npm 允许开发者从包仓库中查找、安装、更新和管理包。这些包仓库通常包含了大量的开源包,由社区成员创建和维护。

一个 Node.js 包通常包含一个 package.json 文件作为包管理配置文件,该文件是包的元数据文件,包含了包的名称、版本、描述、入口文件、依赖关系等信息。这个文件对于 npm 来说是至关重要的,它使得 npm 能够正确地识别、安装和管理包。每个 npm 的安装包中都会包含一个 package.json 文件,通常这个文件位于包的根目录下。

package.json 文件主要包含关于包的元数据(如项目名称和说明)以及功能元数据(如程序包版本号和程序所需的依赖项列表)。其中,名称(name)和版本号(version)是必填项,其他如应用描述(description)、应用的配置项(config)、作者(author)、资源仓库地址(repository)、授权方式(licenses)、目录(directories)、包入口文件(main)、命令行文件(bin)、应用依赖模块(dependencies)、开发环境依赖模块(devDependencies)、运行引擎(engines)和脚本(scripts)等都是可选配置项。

{  
  "name": "my-nodejs-app",  
  "version": "1.0.0",  
  "description": "A simple Node.js application",  
  "main": "index.js",   
  "author": "stone",  
  "license": "MIT",   
}

注意:导入包时,默认的入口文件是 index.js 或者 main 指定的模块文件。

npm

Node.js 的 npmopen in new window(Node Package Manager)是一个强大的包管理器,用于安装和管理 Node.js 项目中的库和依赖项。npm 使得开发者能够轻松地获取、更新和共享代码,从而加速开发过程,提高开发效率。

以下是 npm 的一些关键特点和功能:

  • 包管理:npm 允许开发者从 npm 注册表中搜索、安装和更新各种 Node.js 包。这些包可能是由其他开发者创建并共享的,包含了各种功能,如路由处理、数据库连接、测试工具等。
  • 依赖管理:npm 能够自动处理项目中的依赖关系。当一个包依赖于其他包时,npm 会递归地安装所有必需的依赖项,并确保它们的版本兼容。这使得开发者能够专注于项目的核心功能,而不必担心依赖项的管理和版本冲突问题。
  • package.json 文件:npm 使用 package.json 文件来记录项目的元数据和依赖项。这个文件包含了项目的名称、版本、描述、入口点、脚本命令以及依赖项列表等信息。通过修改 package.json 文件,开发者可以轻松地管理项目的依赖关系和其他配置。
  • 全局与本地安装:npm 支持全局和本地安装两种方式。全局安装将包安装到系统的全局目录中,使得这些包可以在任何 Node.js 项目中使用。而本地安装则将包安装到当前项目的 node_modules 目录中,仅供该项目使用。
  • npm 注册表:npm 使用一个公共的注册表来存储和分发包。这个注册表包含了大量的开源包,供开发者免费使用。同时,开发者也可以创建私有的注册表,用于存储和管理私有包。
  • 脚本和生命周期钩子:npm 支持在 package.json 文件中定义脚本命令,以便在项目构建、测试和发布等阶段执行特定的任务。此外,npm 还提供了一组生命周期钩子,允许开发者在包的安装、卸载、发布等过程中执行自定义的逻辑。
  • npm 社区:npm 社区非常活跃,拥有大量的开源贡献者和用户。这使得开发者能够轻松地获取帮助、分享经验和贡献代码,共同推动 Node.js 生态系统的发展。

如果是一个新创建的项目,则在包目录(只能是英文路径,且不能有空格)下初始化清单文件:

$ npm init -y

完成后得到 package.json

{
  "name": "npm",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

本地安装软件包,多个包使用空格分隔:

$ npm i dayjs

会自动创建 node_modules 目录,并将软件包下载到该目录,并将该软件包记录到 package.json

创建一个 server.js 文件,使用软件包:

const dayjs = require('dayjs')
const newDateStr = dayjs().format('YYYY-MM-DD')
console.log(newDateStr)

如果是一个别人的项目,且项目中没有 node_modules 目录,则需要使用 npm 安装 package.json 中记录的所有软件包:

$ npm i

前面安装的软件包都是本地软件包,还可以安装全局软件包,区别如下:

  • 本地软件包:当前项目内使用,封装属性和方法,存在于 node_modules 目录,还可分为:
    • 开发依赖包:记录到 package.jsondevDependencies 节点中,只在开发期间会用到,需要使用 -D 或者 --save-dev 选项安装
    • 核心依赖包:记录到 package.jsondependencies 节点中,不仅在开发期间会用到,在项目上线之后也会用到。
  • 全局软件包:本机所有项目使用,封装命令和工具,存在于系统设置的位置,例如 c:\Users\用户目录\AppData\Roaming\npm\node_modules。需要使用 -g 选项安装

默认情况下,使用 npm i 命令会自动安装最新版本的包,如果需要安装指定版本的包,可以在包名之后,通过 @ 符号指定具体的版本,并会覆盖之前安装的版本,例如:

$ npm i moment@2.22.2

使用 npm uninstall 命令卸载包:

$ npm uninstall moment

在使用 npm 下载包时,默认从国外的 https://registry.npmjs.org/ 服务器进行下载,下载速度很慢,使用 npm config get registry 查看当前的下包镜像源:

$ npm config get registry
https://registry.npmjs.org/

使用 npm config set registry 设置下包镜像源:

$ npm config set registry=https://registry.npmmirror.com

使用 npm config get registry 查看是否配置成功:

$ npm config get registry
https://registry.npmmirror.com/

yarn

yarn 是一个广受欢迎的开源包管理器,用于管理 JavaScript 项目中的依赖项。相比 npm,速度更快。

安装:

$ npm install --global yarn

查看版本:

$ yarn --version
1.22.22

开始新项目:

$ yarn init

添加依赖

$ yarn add [package]
$ yarn add [package]@[version]
$ yarn add [package]@[tag]

将依赖添加到不同类别的依赖,分别添加到 devDependenciespeerDependenciesoptionalDependencies

$ yarn add [package] -D
$ yarn add [package] -P
$ yarn add [package] -O

升级依赖:

$ yarn upgrade [package]
$ yarn upgrade [package]@[version]
$ yarn upgrade [package]@[tag]

删除依赖:

$ yarn remove [package]

安装项目所有依赖:

$ yarn

或者:

$ yarn install

pnpm

img

pnpm 是一个高效的 JavaScript 包管理器,旨在通过创新的设计来解决传统包管理器(如 npm 和 yarn)中的一些问题。以下是 pnpm 的主要特点和优势:

  • 磁盘空间效率:pnpm 通过内容寻址存储和硬链接来共享包的不同版本,从而极大地节省了磁盘空间。这种设计使得 pnpm 在处理大型项目或具有多个子项目的 monorepo 时能够显著减少所需的存储空间。
  • 快速安装:由于 pnpm 的存储结构,它通常能够更快地安装项目的依赖项。这是因为 pnpm 避免了重复下载和存储相同的包,而是利用现有的缓存和硬链接来快速安装。
  • 确定的依赖解析:pnpm 通过内容寻址来确保安装的依赖项是确定的。这意味着无论在哪个时间或哪个机器上运行 pnpm,都将得到相同的结果。这有助于减少由于依赖项版本不一致而导致的问题。
  • 与 npm 和 yarn 兼容:尽管 pnpm 采用了不同的存储和安装机制,但它仍然与 npm 和 yarn 的生态系统兼容。这意味着你可以使用相同的 package.json 文件格式和 node_modules 目录结构,并且大多数 npm 和 yarn 命令也可以在 pnpm 中使用。
  • 更少的依赖冲突:由于 pnpm 的设计,它通常能够减少依赖项之间的冲突。通过更加精确地控制依赖项的版本和安装位置,pnpm 可以帮助开发者避免一些常见的依赖问题。
  • 轻量级和易于使用:pnpm 的安装和配置相对简单,而且它本身也是一个轻量级的工具,不会给系统带来过多的负担。

安装:

$ npm install -g pnpm

查看版本:

$ pnpm --version
9.0.6

开始新项目:

$ pnpm create

添加依赖

$ pnpm add [package]
$ pnpm add [package]@[version]
$ pnpm add [package]@[tag]

将依赖添加到不同类别的依赖,分别添加到 devDependenciesoptionalDependencies

$ pnpm add [package] -D
$ pnpm add [package] -O

升级依赖:

$ pnpm update [package]
$ pnpm update [package]@[version]
$ pnpm update [package]@[tag]

删除依赖:

$ pnpm remove [package]

安装项目所有依赖:

$ pnpm i

或者:

$ pnpm install

npm,yarn,pnpm 命令对比:

npmyarnpnpm
安装所有依赖npm installyarnpnpm install
安装指定依赖npm intall axiosyarn add axiospnpm add axios
全局安装依赖npm intall axios -Dyarn add axios -Dpnpm add axios -D
删除依赖npm uninstall axiosyarn remove axiospnpm remove axios
启动项目npm run devyarn devpnpm dev
上次编辑于:
贡献者: stonebox,stone