NodeJS-基础学习

520 阅读15分钟

NodeJS-基础学习

本文介绍 NodeJS 相关的基础知识:特点、设计理念、应用场景、常用框架、安装调试、异步编程、事件循环和核心模块等方面。

供自己以后查漏补缺,也欢迎同道朋友交流学习。

引言

NodeJS 我每隔断时间就会看一看,但了解的非常浅显,没有给企业开发过项目。我写这篇文章的目的也是要系统性的学习下 NodeJs 并分享给大家,后面也会写一篇关于 NestJS 搭建基础服务的教程,也请大家多多关注。

完全 0 基础学习 NodeJS 是非常痛苦的,你至少要了解原生 JavaScript 的语法和 ES6 的一些基础知识。

可以详看 JS 随笔

可以详看 (ES6+) 随笔

为什么学习NodeJS

对于前端来说,学习 NodeJs 是比较轻松的,都是基于 JS 开发的,语法不用重新学习;同时学习完服务端,也给我们前端同学打开了全栈的窗口,可以以此为契机去尝试独立开发完整的企业项目,不仅仅是前端页面,还可以尝试后端的实现,比如数据库连接服务器配置缓存日志权限等,还有各种中间件、框架。

学习完 Node 可以轻松的完成一个简单的Web应用,实现全栈开发,也可以出去接活儿。在未来找工作时,也是一个比较大的优势,目前很多公司已经用 Node 做 BFF 层了,也用 Node 做一些后端服务。

NodeJS简介

Node 是一个开源、跨平台的 JS 运行时环境,它允许开发者在服务器端运行 JS 代码。由 Ryan Dahl2009 年创建,基于 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 服务端框架中,ExpressKoaEggNest 都是流行的选择,每个框架都有其特点和适用场景:

Express

  • 是最流行的 Node 应用框架之一,提供极简且灵活的方式构建 Web 应用和 API。
  • 拥有强大的路由功能和中间件支持,可以快速构建 RESTful API
  • 社区庞大,有大量的第三方中间件可供选择。
  • 适合快速开发中小型应用,以及需要大量社区支持和资源的项目。

Koa

  • Express 原班人马打造,致力于成为一个更小、更富有表现力、更健壮的 Web 应用和 API 开发基础。
  • 利用异步函数,避免了传统回调函数的复杂性,并增强了错误处理能力。
  • Koa 没有捆绑任何中间件,提供了一套优雅的方法来编写服务端应用程序。
  • 适合希望使用最新 ECMAScript 特性和需要高度灵活性的开发者。

Egg

  • 由阿里巴巴团队开发,面向企业级应用的框架,基于 Koa 并提供丰富的插件生态。
  • 强调模块化设计约定优于配置 的原则,适合大型项目和团队协作。
  • 提供了一套完善的开发工具和开发流程,便于企业级应用的开发和维护。
  • 适合需要构建大型可维护的企业级应用。

NestJS

  • 是一个用于构建高效可扩展的服务器端应用的框架。
  • 使用 TypeScript 编写,结合了面向对象编程函数式编程函数式响应式编程的元素。
  • 提供了模块化依赖注入中间件等全功能框架特性。
  • 适合构建大型应用,特别是需要强类型面向对象设计的项目。

从上面的框架比较来说,建议初学者学习基础的 Express 进行初步了解,然后花更多的精力去学习 EggNestJS 做企业级应用,尤其是想要去大公司面试和工作的同学。

当然也需要结合实际情况去学习,看看公司选择了啥框架就学什么,先就业。

NestJS可以看看 NodeJS-NestJS基础

环境安装

安装 Node 要根据不同的操作系统来选择,这里以 Mac 为例。

安装Node

  1. 访问Node官网下载安装。
  2. 下载 macOS 版本的 Node.js 安装程序,最好下载最新的稳定版本。
  3. 打开下载的 .pkg 文件并按照安装向导的指示完成安装。
  4. 在安装过程中勾选 “Add to PATH”(添加到 PATH)。
  5. 重启你的终端或计算机

验证安装是否成功:

node -v
npm -v

如果要安装多个版本的node,可以使用 nvm 来管理。

# 查看版本
nvm ls
# 下载版本
nvm install <version>
# 切换版本
nvm use <version>

npm介绍

npm(Node Package Manager,Node 包管理器)是一个包管理器,它是 NodeJS 工具生态系统中的核心组件。npm 用于管理项目中的依赖关系,安装第三方库,以及发布自己的包。以下是 npm 的一些主要特性和用途:

  1. 依赖管理:npm 可以定义项目所需的依赖库,并确保这些依赖项的版本正确安装和更新。
  2. 包仓库:npm 拥有世界上最大的软件注册表,包含超过百万的 JS 包,这些包可以很容易地通过 npm 安装使用。
  3. 版本控制:npm 遵循语义化版本控制(Semantic Versioning),允许开发者理解依赖项的版本变化。
  4. 安装和管理包:使用 npm install 命令可以安装项目依赖,npm uninstall 命令可以移除不再需要的包。
  5. 本地开发:通过 npm link 命令,可以在本地测试和使用自己的包。
  6. 包发布:开发者可以将自己的项目发布为 npm 包,供其他开发者使用。
  7. 初始化项目npm init 命令可以帮助你快速生成项目的 package.json 文件,定义项目的元数据和依赖关系。
  8. 脚本运行:在 package.json 中定义的脚本可以通过 npm run 命令执行,方便自动化工作流程。
  9. 配置管理:npm 允许用户配置 npm 的行为,例如设置默认的包安装目录、缓存目录等。
  10. 工作空间:npm 支持在同一个仓库中管理多个包,通过 package.json 文件中的 workspaces 字段配置。
  11. 安全性:npm 提供了一些安全特性,比如安全漏洞扫描和自动更新依赖项。
  12. 交互式界面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 请求和响应。
  • httpsHTTPS 模块,是 HTTP 模块的安全版本。
  • path:用于处理文件路径,如拼接路径、获取路径名等。
  • stream:用于处理流式数据,如文件上传、下载等。
  • url:用于处理 URL,如解析、格式化等。
  • querystring:用于处理 URL 查询字符串,如解析、格式化等。
  • util:提供一些工具函数,如格式化输出、合并对象等。
  • events:用于创建和监听事件,实现事件驱动编程。
  • os:提供操作系统相关的信息,如操作系统类型、CPU 信息等。
  • domain:用于处理异步错误,如错误捕获、错误处理等。
  • buffer:用于处理二进制数据,如创建、读取、写入等。
  • string_decoder:用于解码 Buffer 对象,如 utf8base64 等。
  • zlib:用于压缩解压缩数据,如 gzipdeflate 等。
  • crypto:用于加密解密签名数据,如 HMACAESRSA 等。
  • cluster:用于创建集群,实现负载均衡分布式计算
  • net:用于创建 TCPUDP 服务器,实现网络通信
  • 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 应用可以做得非常复杂,这个例子只是一个起点。你可以学习如何使用框架(如 ExpressEggNest)、数据库、模板引擎等来构建更完整的应用程序。

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 文件:

写入文件2

使用 Apifox 增加读取文件接口:

读取文件

使用 Apifox 增加删除文件接口:

删除文件

除了上述异步API,fs还有其他读写删除等方式:

  • 同步APIreadFileSyncwriteFileSyncunlinkSyncrenameSync
  • 流APIcreateReadStreamcreateWriteStream

事件(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 });
  });
});

MySQLnavicat 自行找官网下载,新建一个名为 node-demo 的数据库,再新建一个 user 表,自行插入点数据:

user表

通过 apifox 查看接口调用:

user表

接口联调(Apifox)

目前,我基本上都是使用 Apifox 进行接口联调,Apifox = Postman + Swagger + Mock + JMeter

Apifox

它的特点如下:

  • 接口管理:允许你管理和组织你的 API 接口,支持接口的分组层级结构
  • 接口调试:提供了一个用户友好的界面来发送请求、查看响应和调试 API。
  • 自动化测试:支持接口的自动化测试,可以编写测试用例并批量执行。
  • 数据模型:支持定义数据模型,用于生成请求体和验证响应数据。
  • 文档生成:可以生成接口文档,支持多种格式,方便团队成员共享和查看。
  • Mock 服务:提供 Mock 服务,可以在后端服务未完成时模拟接口响应。
  • 环境管理:支持不同环境的配置管理,方便在开发、测试和生产环境中切换。
  • 插件系统:支持插件扩展,可以根据需要添加新的功能。
  • 跨平台:支持 Windows、macOS 和 Linux 操作系统。

下载地址:apifox.com/

参考文档