Node.js 基础

发布于:2024-05-05 ⋅ 阅读:(27) ⋅ 点赞:(0)

第 1 章:了解 Node.js

Node.js 是什么

Node.js 是一个开源且跨平台的 JavaScript 运行时环境,它允许开发者在服务器端运行 JavaScript 代码。Node.js 不是一种编程语言或框架,而是一个环境,使得JavaScript能够脱离浏览器被用于服务端编程。Node.js 使用Google开发的V8 JavaScript引擎,这是同一引擎Google Chrome浏览器使用的。

事件驱动和非阻塞 I/O

Node.js 的核心特性之一是事件驱动和非阻塞I/O模型。这意味着Node.js可以在不等待I/O操作(例如读取文件、数据库操作等)完成的情况下继续执行代码。这种模式可以提高性能和吞吐量,因为它允许单个Node.js进程同时处理多个操作。

Node.js 的架构和事件循环

Node.js 架构的核心是事件循环。事件循环是一个在Node.js进程中运行的循环,它负责监听事件并根据事件触发回调函数。这种机制使得Node.js可以非常高效地处理并发,即使在单线程上也能处理大量的并发连接。事件循环配合Node.js的非阻塞I/O模型,使得Node.js非常适合构建高性能的网络应用。

第 2 章:开始使用 Node.js

Hello World 示例

在Node.js中编写“Hello World”程序是开始学习这个平台的一个好方法。以下是一个简单的例子:

const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello World\n');
});

server.listen(3000, () => {
  console.log('Server running at http://localhost:3000/');
});

在这个例子中,我们导入了Node.js的http模块,创建了一个服务器,它在收到请求时发送"Hello World"响应,并在端口3000上监听。

使用 npm 管理依赖

npm(node package manager)是Node.js的包管理工具,用于管理项目中的依赖。你可以使用npm来安装、共享和管理依赖库。

例如,如果你想安装一个名为express的包,你可以在命令行中运行以下命令:

npm install express

这会在你的项目目录下创建一个node_modules文件夹,并且express包会被下载到这个文件夹中。同时,express的条目会被添加到package.json文件中的dependencies部分,这个文件跟踪了项目的依赖。

理解模块系统

Node.js 使用CommonJS模块系统,每个文件都被视为一个独立的模块。一个模块可以导出对象、函数、变量等,以便其他模块可以使用。

例如,如果你有一个名为greet.js的文件,你可以这样导出一个函数:

// greet.js
module.exports = function() {
  console.log('Hello world!');
};

然后在另一个文件中,你可以使用require函数来导入这个模块:

// app.js
const greet = require('./greet');
greet(); // 输出: Hello world!

通过模块系统,Node.js 支持代码的封装和重用,有助于构建结构化和可维护的应用程序。

第 3 章:核心模块

文件系统(fs)

Node.js 的文件系统(fs)模块提供了一套用于与文件系统交互的API。这包括创建、读取、写入、删除文件和目录等操作。fs模块支持同步和异步API,异步API通过回调函数、Promise或async/await来处理异步操作结果。

基本使用

  • 读取文件:使用fs.readFile函数异步读取文件内容。
const fs = require('fs');

fs.readFile('example.txt', 'utf8', (err, data) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(data);
});
  • 写入文件:使用fs.writeFile函数异步写入文件。
const fs = require('fs');

const content = 'Some content!';
fs.writeFile('example.txt', content, err => {
  if (err) {
    console.error(err);
    return;
  }
  console.log('File has been written');
});
  • 追加文件:使用fs.appendFile函数异步追加内容到文件。
const fs = require('fs');

const content = 'Appending this content!';
fs.appendFile('example.txt', content, err => {
  if (err) {
    console.error(err);
    return;
  }
  console.log('Content appended');
});
  • 删除文件:使用fs.unlink函数异步删除文件。
const fs = require('fs');

fs.unlink('example.txt', err => {
  if (err) {
    console.error(err);
    return;
  }
  console.log('File deleted');
});

目录操作

  • 创建目录:使用fs.mkdir异步创建目录。
const fs = require('fs');

fs.mkdir('myDirectory', { recursive: true }, err => {
  if (err) {
    console.error(err);
    return;
  }
  console.log('Directory created');
});
  • 读取目录:使用fs.readdir异步读取目录内容。
const fs = require('fs');

fs.readdir('myDirectory', (err, files) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(files);
});
  • 删除目录:使用fs.rmdir异步删除目录。
const fs = require('fs');

fs.rmdir('myDirectory', { recursive: true }, err => {
  if (err) {
    console.error(err);
    return;
  }
  console.log('Directory removed');
});

同步与异步

fs模块提供了同步版本的大多数函数,例如readFileSyncwriteFileSync等。同步函数会阻塞Node.js事件循环,直到操作完成。这在某些情况下(如脚本启动时的配置文件读取)是可接受的,但在处理HTTP请求等场景中,应优先使用异步API以避免阻塞。

注意:在使用fs模块进行文件操作时,应该总是考虑到错误处理。在异步函数的回调中,错误对象(如果有的话)通常作为第一个参数传递。

HTTP 模块

Node.js 的 HTTP 模块是一个核心模块,用于创建HTTP服务器和客户端。这个模块提供了一套API,允许Node.js应用程序处理HTTP请求和响应。使用HTTP模块,开发者可以轻松构建能够处理Web请求的应用程序,包括Web服务器、RESTful API和更多。

创建 HTTP 服务器

以下是一个使用 HTTP 模块创建基本 HTTP 服务器的示例。这个服务器监听3000端口,对所有请求返回一个简单的“Hello, World!”响应。

const http = require('http');

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello, World!\n');
});

server.listen(3000, () => {
  console.log('Server running at http://localhost:3000/');
});

在这个示例中,http.createServer()方法用于创建一个新的HTTP服务器实例。这个方法接受一个回调函数,该函数在服务器收到请求时被调用。回调函数接受两个参数:req(请求对象)和res(响应对象)。通过调用res对象的方法,我们可以设置响应状态码、响应头,并发送响应体给客户端。

处理不同的请求路径

在实际应用中,你可能需要根据请求的URL路径来提供不同的响应。以下是一个简单的示例,展示了如何根据请求的路径来返回不同的响应。

const http = require('http');

const server = http.createServer((req, res) => {
  if (req.url === '/') {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Home Page\n');
  } else if (req.url === '/about') {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('About Page\n');
  } else {
    res.statusCode = 404;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Not Found\n');
  }
});

server.listen(3000, () => {
  console.log('Server running at http://localhost:3000/');
});

创建 HTTP 客户端

HTTP 模块也可以用来创建HTTP客户端,这允许Node.js应用程序作为客户端发送HTTP请求。

以下是一个使用http.get()方法发送GET请求的示例:

const http = require('http');

http.get('http://example.com', (res) => {
  let data = '';

  // 接收数据块并拼接
  res.on('data', (chunk) => {
    data += chunk;
  });

  // 数据接收完毕
  res.on('end', () => {
    console.log(data);
  });

}).on('error', (err) => {
  console.log('Error: ' + err.message);
});

在这个示例中,http.get()方法用于发送一个GET请求到指定的URL。这个方法返回一个请求对象,并接受一个回调函数,该函数在收到响应时被调用。响应对象(res)是一个可读流,可以使用事件监听器来处理数据接收过程。

Node.js 的 HTTP 模块提供了强大的功能,用于构建HTTP服务器和客户端。通过这个模块,你可以处理HTTP请求、发送响应、管理HTTP头部、处理不同的请求方法和URL路径,以及创建Web服务和API。这只是Node.js提供的众多功能中的一小部分,但对于Web开发来说是非常核心和重要的部分。

事件(Events)

Node.js 的事件(Events)模块是一个核心模块,它允许创建、触发和监听自定义事件。这是Node.js事件驱动架构的基础,允许不同部分的应用程序通过事件进行通信。事件模块提供了EventEmitter类,它是所有能发射或监听事件的对象的基础。

基本使用

首先,你需要引入events模块,并创建一个EventEmitter实例:

const EventEmitter = require('events');
const eventEmitter = new EventEmitter();

然后,你可以使用.on()方法监听事件,并使用.emit()方法触发事件:

// 监听事件
eventEmitter.on('start', () => {
  console.log('事件已触发');
});

// 触发事件
eventEmitter.emit('start');

传递参数

emit()方法允许你传递任意数量的参数到监听器函数:

eventEmitter.on('start', (num1, num2) => {
  console.log(`事件已触发,参数是 ${num1}${num2}`);
});

eventEmitter.emit('start', 23, 42);

只监听一次

如果你只想让监听器执行一次,可以使用.once()方法代替.on()方法:

eventEmitter.once('firstTime', () => {
  console.log('这个事件只会触发一次');
});

// 第一次触发,监听器会被调用
eventEmitter.emit('firstTime');

// 第二次触发,监听器不会被调用
eventEmitter.emit('firstTime');

移除监听器

你可以使用.removeListener().off()(Node.js 10.0.0+)方法移除事件监听器:

const callback = () => {
  console.log('这个事件将被移除');
};

eventEmitter.on('temp', callback);

// 移除监听器
eventEmitter.removeListener('temp', callback);
// 或者 eventEmitter.off('temp', callback);

eventEmitter.emit('temp'); // 因为监听器已被移除,这里不会有输出

错误事件

EventEmitter实例在遇到错误时会触发一个特殊的error事件。如果没有监听这个事件,Node.js会打印堆栈跟踪并退出程序:

eventEmitter.on('error', (err) => {
  console.error('有错误发生:', err);
});

eventEmitter.emit('error', new Error('哦,不!出错了!'));

事件模块是Node.js异步事件驱动架构的核心。通过使用EventEmitter类,你可以在你的应用程序中创建复杂的事件处理机制。这对于构建高效、可扩展的Node.js应用程序是非常重要的。

路径(Path)模块

Node.js 的 Path 模块提供了一套用于处理文件和目录的路径的实用工具。这个模块可以帮助你构建跨平台的文件系统路径,使得你的代码可以无缝地在不同的操作系统(如 Windows、Linux 和 macOS)之间运行。使用 Path 模块,你可以执行多种路径操作,比如路径的合并、解析、标准化等。

引入 Path 模块

首先,你需要引入 Path 模块:

const path = require('path');

路径合并

使用 path.join() 方法可以合并多个路径片段:

const directory = 'Users';
const fileName = 'example.txt';

const fullPath = path.join(directory, 'Documents', fileName);
console.log(fullPath); // 'Users/Documents/example.txt',在 Windows 上可能是 'Users\Documents\example.txt'

路径解析

path.resolve() 方法可以将路径或路径片段的序列解析为绝对路径:

console.log(path.resolve('Users', 'Documents', 'example.txt'));
// 如果当前工作目录是 '/home/username',则输出 '/home/username/Users/Documents/example.txt'

获取路径的各个部分

Path 模块提供了多个方法来获取路径的各个部分,如目录名、文件名、扩展名等:

const filePath = '/Users/Documents/example.txt';

console.log(path.dirname(filePath));  // '/Users/Documents',获取目录名
console.log(path.basename(filePath)); // 'example.txt',获取文件名
console.log(path.extname(filePath));  // '.txt',获取文件扩展名

路径标准化

path.normalize() 方法可以用来标准化给定的路径,解析 '..' 和 '.' 片段:

console.log(path.normalize('/Users//Documents/../example.txt')); // '/Users/example.txt'

判断是否为绝对路径

path.isAbsolute() 方法可以用来判断给定的路径是否是绝对路径:

console.log(path.isAbsolute('/Users/Documents'));  // true
console.log(path.isAbsolute('./example.txt'));     // false

路径分隔符

Path 模块还提供了属性来获取或设置平台特定的路径分隔符:

  • path.sep 提供了路径分隔符(Windows 上是 '\',POSIX 上是 '/')。
  • path.delimiter 提供了环境变量路径分隔符(Windows 上是 ';',POSIX 上是 ':')。
console.log(path.sep); // 在 POSIX 上输出 '/',在 Windows 上输出 '\\'

Path 模块是 Node.js 中非常有用的一个核心模块,它提供了一系列工具函数,用于处理和转换文件系统路径。这些工具可以帮助你编写更加健壯和可移植的代码,尤其是在需要处理不同操作系统上的文件路径时。

流(Streams)

在 Node.js 中,流(Streams)是处理读写数据的一个抽象概念。流可以将数据从一个地方传输到另一个地方,比如从文件读取数据到内存中,或者从一个 HTTP 请求中发送数据到客户端。使用流可以有效地处理大量数据,因为你不需要一次性将所有数据加载到内存中,而是可以分批次地处理数据。

Node.js 提供了几种基本的流类型:

  • 可读流(Readable Streams):允许 Node.js 从一个数据源(如文件、HTTP 请求等)逐块读取数据。
  • 可写流(Writable Streams):允许 Node.js 将数据逐块写入到目标(如文件、HTTP 响应等)中。
  • 双工流(Duplex Streams):同时实现了 ReadableWritable 接口,可以在同一个通道中同时读写数据。
  • 转换流(Transform Streams):是一种特殊的双工流,可以在读写过程中修改或转换数据。

示例和用法

创建和使用可读流

const fs = require("fs");

// 创建文件并写入内容
const content = `
即使敌众我寡,末将亦能万军丛中取敌将首级。
只有行动才能说明一切。
击鼓,进军。
勇往直前。
胜利在呼唤。
德玛西亚人从不退缩。
勇士之魂 从未破灭。
即将凯旋。
交给我了。
奉吾王之命。
`;
fs.writeFile('example.txt', content, err => {
  if (err) {
    console.error(err);
    return;
  }
  console.log('File has been written');

  // 在写入完成的回调函数中创建一个可读流
  const readableStream = fs.createReadStream("./example.txt");

  // 监听 'data' 事件来读取数据块
  readableStream.on("data", (chunk) => {
    console.log(`Received ${chunk.length} bytes of data.`);
  });

  // 监听 'end' 事件来知道数据已经读取完毕
  readableStream.on("end", () => {
    console.log("No more data to read.");
  });
});

创建和使用可写流

const fs = require('fs');

// 创建一个可写流
const writableStream = fs.createWriteStream('./output.txt');

// 使用 write() 方法写入数据
writableStream.write('德玛西亚人从不退缩。\n');
writableStream.write('即将凯旋。');

// 使用 end() 方法结束写入
writableStream.end(() => {
  console.log('Finished writing to file.');
});

管道(Piping)

管道是一种将可读流中的数据直接传输到可写流中的机制。这是处理流数据的一种高效方式。

const fs = require('fs');

// 创建一个可读流和一个可写流
const readableStream = fs.createReadStream('./example.txt');
const writableStream = fs.createWriteStream('./output.txt');

// 使用 pipe() 方法将可读流中的数据传输到可写流中
readableStream.pipe(writableStream);

console.log('Piping data from example.txt to output.txt');

流是 Node.js 中处理大量数据,尤其是在读写文件或网络通信时的一种高效方式。通过使用流,你可以减少内存的使用,提高应用的性能。Node.js 的流模块提供了丰富的 API 来创建和操作不同类型的流,包括可读流、可写流、双工流和转换流,以及通过管道连接流的能力。

缓冲(Buffers)

在 Node.js 中,Buffer 类是一个全局可用的类,用于直接操作内存中的数据。Buffer 实例类似于整数数组,但它们是固定大小的,并且存储在 V8 堆外的原始内存中。Buffer 被设计用来处理二进制数据,每个元素是一个字节(8位,取值范围从 0 到 255)。

Buffers 主要用于处理如 TCP 流或文件系统操作等 IO 操作中的二进制数据流,以及与其他语言编写的库的交互。

创建 Buffer

使用 Buffer.from()Buffer.alloc()Buffer.allocUnsafe() 方法创建 Buffer:

// 从字符串创建 Buffer
const bufFromString = Buffer.from('hello world', 'utf8');

// 创建一个长度为 10 的填充了 0 的 Buffer
const bufAlloc = Buffer.alloc(10);

// 创建一个长度为 10 的未初始化的 Buffer(可能包含旧数据)
const bufAllocUnsafe = Buffer.allocUnsafe(10);

Buffer 操作

读写 Buffer:

// 写入 Buffer
bufAlloc.write('hello');

// 读取 Buffer
console.log(bufAlloc.toString()); // hello

访问 Buffer 中的数据:

const buf = Buffer.from('hello world', 'utf8');

// 访问 Buffer 中的元素
console.log(buf[0]); // 104('h' 的 ASCII 码)

// 遍历 Buffer
for (const byte of buf) {
  console.log(byte); // 输出 'hello world' 中每个字符的 ASCII 码
}

Buffer 与 JSON:

const buf = Buffer.from("hello world", "utf8");

// 将 Buffer 转换为 JSON
const json = JSON.stringify(buf);

// 将 JSON 转换回 Buffer
const copy = JSON.parse(json, (key, value) => {
  return value && value.type === "Buffer" ? Buffer.from(value.data) : value;
});

console.log(copy);
console.log(json);

Buffer 与字符编码

默认情况下,Buffer 使用 UTF-8 编码,但也支持其他编码,如 asciiutf16lebase64hex 等。

const buf = Buffer.from('hello world', 'ascii');

// 使用不同编码输出 Buffer
console.log(buf.toString('hex')); // 68656c6c6f20776f726c64
console.log(buf.toString('base64')); // aGVsbG8gd29ybGQ=

Buffer 类在 Node.js 中是处理二进制数据的核心工具。它提供了一种方式来表示固定长度的字节序列,非常适合处理网络协议、二进制文件格式和其他需要处理大量二进制数据的场景。通过 Buffer,开发者可以高效地进行数据的读写和转换,尤其是在性能至关重要的应用中。


网站公告

今日签到

点亮在社区的每一天
去签到