NodeJS-基础学习
本文介绍 NodeJS 相关的基础知识:特点、设计理念、应用场景、常用框架、安装调试、异步编程、事件循环和核心模块等方面。
供自己以后查漏补缺,也欢迎同道朋友交流学习。
引言
NodeJS 我每隔断时间就会看一看,但了解的非常浅显,没有给企业开发过项目。我写这篇文章的目的也是要系统性的学习下 NodeJs 并分享给大家,后面也会写一篇关于 NestJS 搭建基础服务的教程,也请大家多多关注。
完全 0 基础学习 NodeJS 是非常痛苦的,你至少要了解原生 JavaScript 的语法和 ES6 的一些基础知识。
可以详看 JS 随笔
可以详看 (ES6+) 随笔
为什么学习NodeJS
对于前端来说,学习 NodeJs 是比较轻松的,都是基于 JS 开发的,语法不用重新学习;同时学习完服务端,也给我们前端同学打开了全栈的窗口,可以以此为契机去尝试独立开发完整的企业项目,不仅仅是前端页面,还可以尝试后端的实现,比如数据库连接、服务器配置、缓存、日志、权限等,还有各种中间件、框架。
学习完 Node 可以轻松的完成一个简单的Web应用,实现全栈开发,也可以出去接活儿。在未来找工作时,也是一个比较大的优势,目前很多公司已经用 Node 做 BFF 层了,也用 Node 做一些后端服务。
NodeJS简介
Node 是一个开源、跨平台的 JS 运行时环境,它允许开发者在服务器端运行 JS 代码。由 Ryan Dahl 于 2009 年创建,基于 Chrome V8 引擎构建的,它的设计目标是提供一种高效、轻量级的服务器端运行环境,能够处理大量并发连接,适用于构建各种网络应用。
Node.js的特点包括:
- 事件驱动:采用
事件驱动模型,可以处理大量的并发连接,而不会阻塞服务器。 - 非阻塞I/O:它使用非阻塞 I/O 模型,这意味着即使在等待
I/O操作完成时,也可以继续处理其他请求。 - 单线程:在单个线程上运行,通过
事件循环和回调函数来管理任务。
设计理念
- 异步编程:Node 鼓励使用
异步编程模式,以避免阻塞主线程,提高性能。 - 事件循环:使用
事件循环来处理I/O操作和事件,确保应用可以持续响应。 - 单线程:尽管 Node 运行在单个线程上,但是通过事件循环和非阻塞 I/O,它可以高效地处理
并发。 - 模块化:采用
CommonJS模块系统,每个模块都是一个单独的文件,可以导出和导入功能。 - 轻量级:旨在保持轻量级,避免不必要的资源消耗,以实现快速启动和运行。
应用场景
Node 由于其轻量级、高性能和事件驱动的特性,适用于多种应用场景。以下是一些常见的 Node 应用场景:
- Web服务器:Node 非常适合处理大量并发请求,创建高性能的
Web服务器。 - 实时应用:如在线聊天室、实时通知系统、在线游戏等,Node 的
非阻塞 I/O特性可以提供快速响应。 - 单页应用(SPA):可以为单页应用提供
API支持和服务器端渲染。 - 命令行工具:创建命令行工具,利用
npm可以轻松分发和安装。 - 网络服务:例如
SMTP服务器、FTP服务器等,Node 可以轻松实现网络协议的处理。 - 数据流处理:Node 的流(
Streams)API非常适合处理大量数据的实时流式传输。 - 微服务架构:可以用于构建微服务,每个服务可以独立部署和扩展。
- 文件上传和下载:Node 的流式接口使得文件
上传和下载变得简单高效。 - 自动化脚本:Node 可以编写自动化脚本,用于持续集成/持续部署(
CI/CD)流程。 - 桌面应用开发:通过
Electron框架可以用来构建跨平台的桌面应用。 - 内容管理系统(CMS):Node 可以用于开发
CMS,提供内容管理的后端逻辑。 - 电子商务平台:Node 的高性能特性使其适合构建
电子商务平台,处理交易和用户会话。 - 社交媒体平台:社交媒体平台需要处理
大量并发用户和实时更新,Node 可以满足这些需求。
常用服务端框架
Node.js 服务端框架中,Express、Koa、Egg 和 Nest 都是流行的选择,每个框架都有其特点和适用场景:
Express:
- 是最流行的 Node 应用框架之一,提供极简且灵活的方式构建 Web 应用和 API。
- 拥有强大的路由功能和中间件支持,可以快速构建
RESTful API。 社区庞大,有大量的第三方中间件可供选择。- 适合快速开发
中小型应用,以及需要大量社区支持和资源的项目。
Koa:
- 由
Express原班人马打造,致力于成为一个更小、更富有表现力、更健壮的 Web 应用和 API 开发基础。 - 利用
异步函数,避免了传统回调函数的复杂性,并增强了错误处理能力。 Koa没有捆绑任何中间件,提供了一套优雅的方法来编写服务端应用程序。- 适合希望使用最新
ECMAScript特性和需要高度灵活性的开发者。
Egg:
- 由阿里巴巴团队开发,面向
企业级应用的框架,基于Koa并提供丰富的插件生态。 - 强调
模块化设计和约定优于配置的原则,适合大型项目和团队协作。 - 提供了一套完善的开发工具和开发流程,便于企业级应用的开发和维护。
- 适合需要构建
大型、可维护的企业级应用。
NestJS:
- 是一个用于构建
高效、可扩展的服务器端应用的框架。 - 使用
TypeScript编写,结合了面向对象编程、函数式编程和函数式响应式编程的元素。 - 提供了
模块化、依赖注入、中间件等全功能框架特性。 - 适合构建
大型应用,特别是需要强类型和面向对象设计的项目。
从上面的框架比较来说,建议初学者学习基础的 Express 进行初步了解,然后花更多的精力去学习 Egg 或 NestJS 做企业级应用,尤其是想要去大公司面试和工作的同学。
当然也需要结合实际情况去学习,看看公司选择了啥框架就学什么,先就业。
NestJS可以看看 NodeJS-NestJS基础
环境安装
安装 Node 要根据不同的操作系统来选择,这里以 Mac 为例。
安装Node
- 访问Node官网下载安装。
- 下载
macOS版本的 Node.js 安装程序,最好下载最新的稳定版本。 - 打开下载的
.pkg文件并按照安装向导的指示完成安装。 - 在安装过程中勾选 “Add to PATH”(添加到 PATH)。
- 重启你的终端或计算机
验证安装是否成功:
node -v
npm -v
如果要安装多个版本的node,可以使用 nvm 来管理。
# 查看版本
nvm ls
# 下载版本
nvm install <version>
# 切换版本
nvm use <version>
npm介绍
npm(Node Package Manager,Node 包管理器)是一个包管理器,它是 NodeJS 工具生态系统中的核心组件。npm 用于管理项目中的依赖关系,安装第三方库,以及发布自己的包。以下是 npm 的一些主要特性和用途:
- 依赖管理:npm 可以定义项目所需的依赖库,并确保这些依赖项的版本正确安装和更新。
- 包仓库:npm 拥有世界上最大的软件注册表,包含超过百万的 JS 包,这些包可以很容易地通过 npm 安装使用。
- 版本控制:npm 遵循语义化版本控制(Semantic Versioning),允许开发者理解依赖项的版本变化。
- 安装和管理包:使用
npm install命令可以安装项目依赖,npm uninstall命令可以移除不再需要的包。 - 本地开发:通过
npm link命令,可以在本地测试和使用自己的包。 - 包发布:开发者可以将自己的项目发布为 npm 包,供其他开发者使用。
- 初始化项目:
npm init命令可以帮助你快速生成项目的package.json文件,定义项目的元数据和依赖关系。 - 脚本运行:在
package.json中定义的脚本可以通过npm run命令执行,方便自动化工作流程。 - 配置管理:npm 允许用户配置 npm 的行为,例如设置默认的包安装目录、缓存目录等。
- 工作空间:npm 支持在同一个仓库中管理多个包,通过
package.json文件中的workspaces字段配置。 - 安全性:npm 提供了一些安全特性,比如安全漏洞扫描和自动更新依赖项。
- 交互式界面:
npm CLI(命令行界面)提供了许多交互式命令,帮助用户更好地管理和使用 npm。
使用 npm 的基本命令如下:
npm install <package>:安装一个包。npm uninstall <package>:卸载一个包。npm update <package>:更新一个包到最新版本。npm list:列出已安装的包。npm search <keyword>:搜索包。npm publish:发布一个包到 npm 注册表。npm init:创建一个package.json文件。
npm 是现代 JS 开发中不可或缺的工具,它极大地简化了依赖管理、代码共享和模块化开发的过程。
NodeJS模块系统
NodeJS 使用 CommonJS 模块系统作为其默认的模块系统。在 CommonJS 中,每个文件都是一个模块,模块可以导出(exports)变量、函数、对象等,也可以导入(require)其他模块的导出内容。
导出模块
我们使用 module.exports 用来导出模块的变量、函数、对象等。
// math.js
function add(x, y) {
return x + y;
}
// 导出函数
module.exports = add;
如果你想要导出多个属性或方法,可以这样:
// math.js
const math = {
add: function(x, y) {
return x + y;
},
subtract: function(x, y) {
return x - y;
}
};
// 导出整个对象
module.exports = math;
导入模块
使用 require 函数来导入其他模块的导出内容。
- 模块路径:
require函数的第一个参数是模块的路径,可以是相对路径或绝对路径。 - 缓存机制:当一个模块被
require一次后,Node.js 会缓存该模块,避免重复加载。
// app.js
const math = require('./math');
console.log(math.add(1, 2)); // 输出 3
内置的核心模块
Node 提供了一系列内置的核心模块,这些模块不需要通过 npm 安装,可以直接通过 require 函数引入使用。以下是一些常用的核心模块:
- fs:用于
文件系统操作,如读写文件、创建目录等。 - http:用于创建
HTTP服务器,处理 HTTP 请求和响应。 - https:
HTTPS模块,是 HTTP 模块的安全版本。 - path:用于
处理文件路径,如拼接路径、获取路径名等。 - stream:用于处理
流式数据,如文件上传、下载等。 - url:用于处理
URL,如解析、格式化等。 - querystring:用于处理 URL 查询
字符串,如解析、格式化等。 - util:提供一些
工具函数,如格式化输出、合并对象等。 - events:用于创建和监听事件,实现
事件驱动编程。 - os:提供
操作系统相关的信息,如操作系统类型、CPU信息等。 - domain:用于处理
异步错误,如错误捕获、错误处理等。 - buffer:用于处理
二进制数据,如创建、读取、写入等。 - string_decoder:用于解码
Buffer对象,如utf8、base64等。 - zlib:用于
压缩和解压缩数据,如gzip、deflate等。 - crypto:用于
加密、解密和签名数据,如HMAC、AES、RSA等。 - cluster:用于创建
集群,实现负载均衡和分布式计算。 - net:用于创建
TCP或UDP服务器,实现网络通信。 - tls:用于创建
TLS/SSL服务器,实现加密通信。 - readline:用于创建
交互式命令行界面,如命令行提示符、输入等。 - vm:用于执行
JS代码,如沙箱环境等。 - dns:用于执行
DNS查询,如解析域名、IP 地址等。 - punycode:用于处理域名的
punycode编码,如 punycode.encode、punycode.decode 等。 - child_process:用于创建和管理
子进程,实现进程间通信。 - console:用于提供简单的
调试控制台。 - assert:用于测试代码的正确性,如
断言等。 - constants:包含各种系统级别的常量,如
文件权限等。 - dgram:用于创建
UDP数据报套接字,实现网络通信。
编写一个NodeJS应用
创建一个简单的 Node 应用程序通常涉及几个步骤:初始化项目、安装依赖、编写代码、运行应用。
以下是一个基本的 Node HTTP 服务器应用程序的示例。
初始化项目:
打开终端或命令提示符,创建一个新目录作为你的项目文件夹,并进入该目录。
mkdir my-node-demo
cd my-node-demo
初始化 npm:
在项目目录中,运行以下命令来创建一个 package.json 文件。
npm init -y
这将生成一个默认的 package.json 文件。
编写应用代码:
使用文本编辑器创建一个名为 app.js 的文件,并添加以下代码:
// app.js
const http = require('http');
// 创建 HTTP 服务器
const server = http.createServer((req, res) => {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello, World!\n');
});
// 服务器监听 3000 端口
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
运行应用:
在终端中,运行以下命令来启动你的 Node 应用:
node app.js
访问浏览器:
打开你的浏览器,访问 http://localhost:3000。你应该看到浏览器显示 "Hello, World!"。
Node 应用可以做得非常复杂,这个例子只是一个起点。你可以学习如何使用框架(如 Express、Egg、Nest)、数据库、模板引擎等来构建更完整的应用程序。
NodeJS核心模块
为了后续案例的代码简洁,下面核心模块的案例都使用 Express,项目根目录安装:
npm install express
为了更好的处理接口,进行路由拆分,新建 routes 文件夹,新建 index.js 文件:
// routes/index.js
const express = require("express");
const router = express.Router();
router.get("/", (req, res) => {
res.send('Hello World');
});
module.exports = router;
修改 app.js:
const express = require('express');
const app = express();
const PORT = 3009;
// 引入路由
const homeRouter = require('./routes/index');
// 注册路由
app.use("/", homeRouter);
// 服务器监听
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}/`);
});
执行node app.js,并访问浏览器 http://localhost:3009 即可看到页面显示 Hello World 字符串。
为了更好的开发体验,进行热更新,简单的调试使用 nodemon,线上可使用 PM2:
npm install nodemon
修改 package.json:
{
"scripts": {
"start": "nodemon app.js"
}
}
后面起服务就直接:
npm run start
对于接口调试我推荐使用Apifox
文件系统(fs)
fs 模块是一个用于文件系统操作的核心模块,它提供了文件的读写、删除、重命名等操作。fs 模块支持同步和异步两种方式。
下面我们写个案例测试下,先在 app.js 增加路由配置:
// ...
// 引入路由
const fsRouter = require('./routes/fs-router');
// 注册路由
app.use("/fs-demo", fsRouter);
// ...
新建 routes/fs-router.js:
const express = require("express");
const router = express.Router();
const { writeFile, readFile, deleteFile } = require("../fs-demo");
router.post("/writeFile", (req, res) => {
writeFile(req, res);
});
router.get("/readFile", (req, res) => {
readFile(req, res);
});
router.post("/deleteFile", (req, res) => {
deleteFile(req, res);
});
module.exports = router;
新建 fs-demo/index.js:
const fs = require("fs");
const path = require("path");
// 构建目标文件路径
const targetPath = path.join(__dirname, "/example.txt");
// 写入文件
const writeFile = (req, res) => {
const data = "Hello, FS!";
fs.writeFile(targetPath, data, 'utf8', (err) => {
if (err) throw err;
res.json({ success: true, message: 'File written successfully' });
});
};
// 读取文件
const readFile = (req, res) => {
fs.readFile(targetPath, 'utf8', (err, data) => {
if (err) throw err;
console.log('@@@ data', data);
res.json({ success: true, message: data });
});
};
// 删除文件
const deleteFile = (req, res) => {
fs.unlink(targetPath, (err) => {
if (err) throw err;
res.json({ success: true, message: "File deleted!" });
});
};
module.exports = { writeFile, readFile, deleteFile };
使用 Apifox 增加写入文件接口:
使用可以看到在fs-demo的文件夹下新增了一个 example.txt 文件:
使用 Apifox 增加读取文件接口:
使用 Apifox 增加删除文件接口:
除了上述异步API,fs还有其他读写删除等方式:
- 同步API:
readFileSync、writeFileSync、unlinkSync、renameSync。 - 流API:
createReadStream和createWriteStream。
事件(events)
events 模块是一个内置模块,它提供了一个名为 EventEmitter 的类,用于处理事件驱动的编程。
以下是如何在 Node.js 中使用 events 模块的基本步骤:
创建一个自定义的 EventEmitter
引入 events 模块:
首先,你需要引入 events 模块。
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
创建实例并触发事件:
创建 MyEmitter 的实例,并使用 emit 方法触发事件。
const myEmitter = new MyEmitter();
myEmitter.on('event', (arg1, arg2) => {
console.log(`监听器被触发,参数1: ${arg1}, 参数2: ${arg2}`);
});
myEmitter.emit('event', '参数1', '参数2');
// 一次性监听器
// 使用 `once` 方法添加一个监听器,它在被触发一次后会自动移除。
myEmitter.once('singleEvent', () => {
console.log('这个监听器只会触发一次');
});
// 监听器的数量
// 使用 `listenerCount` 方法获取特定事件的监听器数量。
console.log(myEmitter.listenerCount('event'));
// 移除监听器
// 使用 `removeListener` 或 `off` 方法移除特定事件的监听器。
const listener = () => {
console.log('这个监听器将被移除');
};
myEmitter.on('removeEvent', listener);
myEmitter.removeListener('removeEvent', listener);
// 错误事件
myEmitter.on('error', (err) => {
console.error('捕获到错误', err);
});
myEmitter.emit('error', new Error('出错了'));
访问MySQL数据库
上面讲的都是基础知识和 node 的接口调用,但我们实操里基本上都要和数据库打交道,其中 MySQL 又是我们接触比较频繁的,当然后面我们前端如果学点 MongoDB 也是挺不错的:
安装 mysql2 包:
npm install mysql2
创建数据库链接:
创建一个名为 db.js 的文件来配置数据库连接:
const mysql = require('mysql2');
// 创建数据库连接池
const pool = mysql.createPool({
host: 'localhost', // 数据库服务器地址
user: 'your_username', // 数据库用户名
password: 'your_password', // 数据库密码
database: 'your_database', // 要连接的数据库名
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// 导出连接池
module.exports = pool;
修改 app.js,加入数据库连接池:
// 头部引入db
const pool = require('./db'); // 引入数据库连接池
// 监听/userList,用于从数据库获取数据
app.get('/userList', (req, res) => {
pool.execute('SELECT * FROM user', (err, results) => {
if (err) {
console.error(err);
return res.status(500).json({ success: false, message: 'Database query failed' });
}
console.log('@@@ results', results);
res.json({ success: true, data: results });
});
});
MySQL 和 navicat 自行找官网下载,新建一个名为 node-demo 的数据库,再新建一个 user 表,自行插入点数据:
通过 apifox 查看接口调用:
接口联调(Apifox)
目前,我基本上都是使用 Apifox 进行接口联调,Apifox = Postman + Swagger + Mock + JMeter:
它的特点如下:
- 接口管理:允许你管理和组织你的 API 接口,支持接口的
分组和层级结构。 - 接口调试:提供了一个用户友好的界面来发送请求、查看响应和调试 API。
- 自动化测试:支持接口的
自动化测试,可以编写测试用例并批量执行。 - 数据模型:支持定义
数据模型,用于生成请求体和验证响应数据。 - 文档生成:可以生成接口文档,支持多种格式,方便团队成员共享和查看。
- Mock 服务:提供
Mock服务,可以在后端服务未完成时模拟接口响应。 - 环境管理:支持不同环境的配置管理,方便在开发、测试和生产环境中切换。
- 插件系统:支持
插件扩展,可以根据需要添加新的功能。 - 跨平台:支持 Windows、macOS 和 Linux 操作系统。
下载地址:apifox.com/