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 -v和 npm -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('模块名或路径')
    • 内置模块直接写模块名(例如:fs,path,http),第三方模块直接写安装时指定的包名
    • 自定义模块写模块文件路径(例如:./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 导出了两个函数 add 和 subtract。然后,在 app.js 中,使用 require 函数引入了 mathUtils 模块,并将其赋值给 mathUtils 变量。之后,就可以通过 mathUtils 变量来调用 add 和 subtract 函数了。

ECMAScript 规范

除了使用 CommonJS 规范(require 和 module.exports)进行模块化外,Node.js 还支持 ECMAScript 模块(ESM),这是 JavaScript 的官方标准模块系统。ESM 是异步加载模块的,这意味着它不会阻塞代码的执行,因此更适合在浏览器环境中使用。使用 ESM,可以通过 import 和 export 关键字来导入和导出模块。但请注意,为了使用 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 导出了两个函数 add 和 subtract。然后,在 app.js 中,使用 import 函数引入了 mathUtils 模块,并将其赋值给 mathUtils 变量。之后,就可以通过 mathUtils 变量来调用 add 和 subtract 函数了。

命名导出导入

可以使用 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.json 的 devDependencies 节点中,只在开发期间会用到,需要使用 -D 或者 --save-dev 选项安装
    • 核心依赖包:记录到 package.json 的 dependencies 节点中,不仅在开发期间会用到,在项目上线之后也会用到。
  • 全局软件包:本机所有项目使用,封装命令和工具,存在于系统设置的位置,例如 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]

将依赖添加到不同类别的依赖,分别添加到 devDependencies、peerDependencies、optionalDependencies:

$ 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]

将依赖添加到不同类别的依赖,分别添加到 devDependencies、optionalDependencies:

$ 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