NodeJS探索系列(二) -- Node.js 的核心模块和 API

576 阅读22分钟

前言

上一章NodeJS探索系列(一) -- Node.js 的基本概念我们探讨了NodeJS基本概念发展历史基本架构运行时的架构安装等等,这一章,我们将来一起研究一下NodeJS的核心模块和API,欢迎点赞,关注!

正文

基本的全局对象和全局变量

JavaScript中,全局对象是指在整个JavaScript程序中都可以访问的对象。它们可以包含全局变量全局函数和其他全局对象JavaScript中有一些内置的全局对象,其中包括:

  • global对象
    • global对象是Node.js中的全局对象,可以在任何地方使用。它是一个全局变量,因此不需要使用require()函数来引入它。
    • global对象中包含了许多有用的属性和方法,例如setTimeout()setInterval()函数,它们可以用来设置定时器。
    • 此外,global对象还包含了一些其他的全局变量和函数。
  • process对象
    • process对象是Node.js中的另一个全局对象,它可以用来访问当前进程的信息和控制当前进程。
    • process对象中包含了许多有用的属性和方法,例如process.argv可以用来获取命令行参数,process.cwd()可以用来获取当前工作目录,process.exit()可以用来退出当前进程等。
    • process对象还可以用来监听进程事件,例如当进程退出时可以监听exit事件。
  • console对象
  • console对象是用来在控制台输出信息的全局对象。它提供了一些方法,例如console.log()可以用来输出日志信息,console.error()可以用来输出错误信息,console.warn()可以用来输出警告信息等。
  • console对象还提供了一些调试工具,例如console.trace()可以用来输出函数调用堆栈信息,console.time()console.timeEnd()可以用来计算函数执行时间等。

image.png

在编写JavaScript程序时,全局对象和全局变量是非常常见的。但是,使用过多的全局变量会导致代码难以维护和扩展。因此,应该尽量避免使用全局变量,而是使用局部变量和函数来组织代码。此外,为了避免命名冲突,应该将变量和函数封装在模块中,而不是直接暴露在全局作用域中。

文件系统和路径处理

Node.js中,文件系统和路径处理是非常重要的功能,因为在开发过程中我们需要读取写入操作文件,同时还需要处理文件的路径。为了实现这些功能,Node.js提供了fs模块和path模块。

fs模块

fs模块是文件系统模块,用于读取写入复制移动重命名删除等操作。要使用fs模块,需要先引入它,可以通过以下方式:

const fs = require('fs');

其中,fsNode.js内置的文件系统模块。

fs模块提供了很多方法,以下是常用的一些方法:

  • 读取文件内容:使用fs.readFile()方法可以读取文件内容。例如,读取名为test.txt的文件内容:
fs.readFile('test.txt', 'utf8', function(err, data) {
  if (err) throw err;
  console.log(data);
});

其中,utf8表示以文本格式读取文件内容,回调函数中的data参数就是文件的内容。

  • 写入文件内容:使用fs.writeFile()方法可以将数据写入文件。例如,将字符串Hello World!写入名为output.txt的文件:
fs.writeFile('output.txt', 'Hello World!', function(err) {
  if (err) throw err;
  console.log('数据已写入文件');
});
  • 创建文件夹:使用fs.mkdir()方法可以创建新的文件夹。例如,创建名为test的文件夹:
fs.mkdir('test', function(err) {
  if (err) throw err;
  console.log('文件夹已创建');
});
  • 读取文件夹中的文件列表:使用fs.readdir()方法可以读取文件夹中的文件列表。例如,读取名为test的文件夹中的文件列表:
fs.readdir('test', function(err, files) {
  if (err) throw err;
  console.log(files);
});

其中,files参数是一个数组,包含了文件夹中所有文件的名称。

  • 删除文件或文件夹:使用fs.unlink()方法可以删除文件,使用fs.rmdir()方法可以删除文件夹。例如,删除名为test.txt的文件:
fs.unlink('test.txt', function(err) {
  if (err) throw err;
  console.log('文件已删除');
});

path模块

path模块是路径处理模块,用于处理文件路径。要使用path模块,需要先引入它,可以通过以下方式:

const path = require('path');

path模块提供了很多方法,以下是常用的一些方法:

  • 获取文件名:使用path.basename()方法可以获取文件名。例如,获取名为/Users/username/test.txt的文件名:
const filename = path.basename('/Users/username/test.txt');
console.log(filename); // 输出:test.txt
  • 获取文件路径:使用path.dirname()方法可以获取文件路径。例如,获取名为/Users/username/test.txt的文件路径:
const dir = path.dirname('/Users/username/test.txt');
console.log(dir); // 输出:/Users/username
  • 获取文件后缀名:使用path.extname()
const dir = path.extname('/Users/username/test.txt');
console.log(dir); // 输出:.txt
  • path.join([...paths]):将所有参数作为路径的一部分连接起来,并返回连接后的路径。该方法会自动去掉多余的路径分隔符,同时处理不同操作系统下的路径分隔符。
  • path.resolve([...paths]):将所有参数作为路径的一部分连接起来,并返回连接后的绝对路径。如果存在相对路径,则会自动将其解析为绝对路径。
  • path.normalize(path):将路径规范化,去掉多余的路径分隔符,处理特殊符号(如 ...),并将路径分隔符转换为当前操作系统下的标准分隔符。

下面是一些使用path模块的示例代码:

const path = require('path');

const fullPath = '/Users/username/Documents/file.txt';

// 上面已经演示过了
console.log(path.basename(fullPath)); // 'file.txt'
console.log(path.dirname(fullPath)); // '/Users/username/Documents'
console.log(path.extname(fullPath)); // '.txt'

const parts = ['Users', 'username', 'Documents', 'file.txt'];
console.log(path.join(...parts)); // 'Users/username/Documents/file.txt'

console.log(path.resolve('/foo/bar', './baz')); // '/foo/bar/baz'

console.log(path.normalize('/foo/bar//baz/asdf/quux/..')); // '/foo/bar/baz/asdf

网络和流处理

网络和流处理是 Node.js 中非常重要的一部分,允许 Node.js 应用程序与其他计算机和设备进行通信,并能够进行文件、数据和视频的传输和处理。以下是 Node.js 中涉及网络和流处理的一些模块:

  • http/https模块
    • Node.js 提供了 http/https 模块来支持网络通信,这些模块使 Node.js 应用程序能够创建和处理 HTTP/HTTPS 服务器和客户端。
    • http 模块可用于创建 HTTP 服务器和客户端,而 https 模块可用于创建 HTTPS 服务器和客户端。
    • 这些模块提供了一系列的方法和事件,使得开发者可以非常灵活地管理网络连接和通信。
http模块创建服务器
const http = require('http')

const hostname = '127.0.0.1'
const port = 3000

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

// 启动服务
server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`)
})
https模块创建服务器
const https = require('node:https')
const fs = require('node:fs')

// 生成的key和cert
// key 是服务器上的私钥文件。
// crt 是证书颁发机构 CA 签名后的证书。
// 如果你创建了CA证书,一般还会有csr文件,csr 是证书请求签名文件,用于提交给证书颁发机构 CA。
const options = {
  key: fs.readFileSync('keys/agent2-key.pem'),
  cert: fs.readFileSync('keys/agent2-cert.pem')
}

https
  .createServer(options, (req, res) => {
    res.writeHead(200)
    res.end('hello https\n')
  })
  .listen(3001)
  • net模块
    • Node.jsnet 模块是用于创建 TCP 服务器和客户端的。

    • 这个模块提供了一些方法和事件,使得开发者可以创建和管理 TCP 连接,并实现一些自定义的网络通信协议。

    • 演示

  • TCP服务器代码
const net = require('net');

const server = net.createServer((socket) => {
  // 新的客户端连接
  console.log('New client connected.');

  // 监听客户端发送的数据
  socket.on('data', (data) => {
    console.log(`Received data from client: ${data}`);

    // 向客户端发送数据
    socket.write(`Server received data: ${data}`);
  });

  // 监听客户端关闭连接
  socket.on('end', () => {
    console.log('Client disconnected.');
  });
});

// 启动服务器,监听指定的端口
server.listen(8080, () => {
  console.log('Server listening on port 8080.');
});
  • TCP客户端代码
const net = require('net');

const client = net.connect({ port: 8080 }, () => {
  // 连接到服务器成功
  console.log('Connected to server.');

  // 向服务器发送数据
  client.write('Hello, server.');
});

// 监听服务器发送的数据
client.on('data', (data) => {
  console.log(`Received data from server: ${data}`);
});

// 监听与服务器的连接断开事件
client.on('end', () => {
  console.log('Disconnected from server.');
});

以上示例代码中,我们使用了 net.createServer() 方法创建了一个 TCP 服务器,并通过监听 socket 对象的 dataend 事件实现了与客户端的通信。同时,我们也使用了 net.connect() 方法创建了一个 TCP 客户端,并通过监听 client 对象的 dataend 事件实现了与服务器的通信。

  • stream模块
    • Node.js 中的 stream 模块提供了一种处理流式数据的方式。流数据是一种持续性的数据,例如从文件或网络连接中读取的数据。
    • Node.js 提供了一些内置的流对象,例如可读流可写流可读写流。这些流对象提供了一些方法和事件,使得开发者可以非常灵活地处理和操作流数据,例如将数据从一个流传输到另一个流,或者将数据缓存到内存中。
    • 流处理在数据处理和网络通信方面都非常有用,可以提高应用程序的性能和可靠性。
    • 演示
const fs = require('fs');
const { Transform } = require('stream');

// 创建可读流
const readable = fs.createReadStream('input.txt');

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

// 创建转换流
const transform = new Transform({
  transform(chunk, encoding, callback) {
    // 将读取的数据转换为大写字母并写入可写流中
    const upperChunk = chunk.toString().toUpperCase();
    this.push(upperChunk);
    callback();
  }
});

// 将可读流连接到转换流,并将转换流连接到可写流
readable.pipe(transform).pipe(writable);

// 监听可写流的 finish 事件,表示写入完成
writable.on('finish', () => {
  console.log('Data has been written to file.');
});

以上示例代码中,我们首先创建了一个可读流 readable,从文件 input.txt 中读取数据;然后创建了一个可写流 writable,将转换后的数据写入到文件 output.txt 中;最后创建了一个转换流 transform,在读取到数据后将其转换为大写字母。然后,我们通过 readable.pipe(transform).pipe(writable) 将可读流 readable 连接到转换流 transform,并将转换流 transform 连接到可写流 writable。这样一来,当我们从可读流 readable 中读取数据时,经过转换流 transform 的处理后,最终写入到了可写流 writable 中。最后,我们监听可写流的 finish 事件,表示写入操作已经完成。

这个例子展示了使用 stream 模块处理流式数据的一个常见场景,即从一个可读流中读取数据,通过转换流进行数据转换,最后将转换后的数据写入到一个可写流中。

操作系统和进程管理

Node.js中,操作系统和进程管理是很重要的一部分。Node.js提供了oschild_process模块来处理这些任务。

  • os模块
    • os模块允许您访问与操作系统相关的信息,例如CPU架构,操作系统类型系统负载内存使用情况等。您可以使用os模块来执行与操作系统相关的任务,例如在操作系统级别上操作文件和目录。

例如,要获取当前操作系统的类型,您可以使用os模块中的type()方法:

const os = require('os');

console.log(os.type()); // MacOS
  • child_process模块
    • child_process模块允许您从Node.js中启动新进程,并与它们进行交互。您可以使用child_process模块来执行与进程相关的任务,例如运行外部命令,通过子进程访问标准输入输出等。

例如,要运行一个外部命令并将其输出打印到控制台,您可以使用child_process模块中的exec()方法:

const { exec } = require('child_process');

exec('ls -la', (error, stdout, stderr) => {
  if (error) {
    console.error(`exec error: ${error}`);
    return;
  }
  console.log(`stdout: ${stdout}`);
  console.error(`stderr: ${stderr}`);
});

在上面的代码中,我们使用exec()方法来运行一个ls -la命令,并将输出打印到控制台。如果有错误发生,我们将打印错误信息到控制台。

再比如利用主线程与子线程之间进行通信,子线程与子线程进行通信,示例代码如下:

主线程

const { fork } = require('child_process')

// 创建子进程 1
const child1 = fork('child1.js')

// 创建子进程 2
const child2 = fork('child2.js')

// 在主线程中与子进程通信
child1.send('Hello from main thread!')
child2.send('Hi from main thread!')

// 监听子进程 1 发来的消息
child1.on('message', (msg) => {
  console.log(`Message from child 1: ${msg}`)
})

// 监听子进程 2 发来的消息
child2.on('message', (msg) => {
  console.log(`Message from child 2: ${msg}`)
})

// 向子进程 1 发送消息
setTimeout(() => {
  child1.send('How are you, child 1?')
}, 1000)

// 向子进程 2 发送消息
setTimeout(() => {
  child2.send('How are you, child 2?')
}, 2000)

// 监听子进程退出事件
child1.on('exit', (code, signal) => {
  console.log(`Child 1 exited with code ${code} and signal ${signal}.`)
})

child2.on('exit', (code, signal) => {
  console.log(`Child 2 exited with code ${code} and signal ${signal}.`)
})

子线程1

// 在子线程 1 中监听主线程发来的消息
process.on('message', (msg) => {
  console.log(`Message from main thread to child 1: ${msg}`)

  // 向主线程发送消息
  process.send('Hello from child 1')
})

// 在子线程 1 中向子线程 2 发送消息
process.send({ to: 'child2', msg: 'Hello from child 1 to child 2' })

子线程2

// 在子线程 2 中监听主线程发来的消息
process.on('message', (msg) => {
  console.log(`Message from main thread to child 2: ${msg}`)

  // 向主线程发送消息
  process.send('Hello from child 2')
})

// 在子线程 2 中向子线程 1 发送消息
process.send({ to: 'child1', msg: 'Hello from child 2 to child 1' })

在这两个文件中,我们分别通过 process.on('message') 监听主线程发来的消息,并通过 process.send() 方法向主线程和其他子线程发送消息。注意,在子线程中不能直接访问主线程的变量和方法,需要通过进程间通信来进行交互。

以上是Node.js中操作系统和进程管理的一些基本信息。您可以在官方文档中了解更多详细信息。

加密和安全

Node.js中,加密和安全是非常重要的主题,特别是在处理敏感信息时。为了确保数据的安全,Node.js提供了几个内置模块,包括cryptotls/ssl

  • crypto模块

    • crypto模块提供了各种加密算法和哈希算法,可以用于生成数字签名、加密和解密数据、生成伪随机数等操作。
    • 其中包含了许多标准的加密算法,例如AESDESRSADiffie-Hellman等,还有一些哈希算法,如MD5SHA-1SHA-256等。

使用crypto模块,我们可以轻松地实现数据加密和解密。例如,以下代码使用AES算法对数据进行加密和解密:

const crypto = require('crypto');

// 定义加密算法和密钥和向量(IV)
const algorithm = 'aes-256-cbc';
const key = crypto.randomBytes(32); // 生成一个32字节的随机密钥
const iv = crypto.randomBytes(16); // 生成一个16字节的随机向量(IV)

// 加密函数
function encrypt(text) {
  const cipher = crypto.createCipheriv(algorithm, key, iv);
  let encrypted = cipher.update(text, 'utf8', 'hex'); // 加密输入文本
  encrypted += cipher.final('hex'); // 加密结束
  return encrypted; // 返回加密后的文本
}

// 解密函数
function decrypt(text) {
  const decipher = crypto.createDecipheriv(algorithm, key, iv);
  let decrypted = decipher.update(text, 'hex', 'utf8'); // 解密输入文本
  decrypted += decipher.final('utf8'); // 解密结束
  return decrypted; // 返回解密后的文本
}

// 定义一个原始文本
const originalText = 'Hello World!';

// 使用加密函数加密原始文本
const encryptedText = encrypt(originalText);

// 使用解密函数解密加密后的文本
const decryptedText = decrypt(encryptedText);

// 打印结果
console.log('Original Text: ' + originalText);
console.log('Encrypted Text: ' + encryptedText);
console.log('Decrypted Text: ' + decryptedText);

  • tls/ssl模块
    • tls/ssl模块提供了安全传输层协议的实现,可以用于建立安全的网络连接。
    • 在使用tls/ssl模块时,我们可以选择使用预先生成的证书,也可以自己生成证书。以下是使用预先生成的证书建立https服务器的示例代码:
const https = require('https');
const fs = require('fs');

const options = {
  key: fs.readFileSync('server-key.pem'),
  cert: fs.readFileSync('server-cert.pem')
};

https.createServer(options, (req, res) => {
  res.writeHead(200);
  res.end('Hello World!');
}).listen(443);

总之,加密和安全在Node.js应用程序中扮演着非常重要的角色,cryptotls/ssl模块提供了广泛的功能,帮助我们确保数据的安全性和完整性。

其他常用模块

除了之前提到的模块,Node.js还有其他一些常用模块。下面是其中几个模块的介绍:

  • events模块:事件模块。该模块提供了一种处理事件的机制。通过该模块,可以定义和触发事件,以及处理事件。
  • util模块:实用工具模块。该模块提供了一些实用的函数,例如继承、类型判断等。
  • querystring模块:查询字符串模块。该模块提供了一些解析和序列化查询字符串的函数。

下面分别对这些模块进行详细介绍:

events模块

事件是Node.js的核心特性之一,而事件模块则提供了一种处理事件的机制。事件模块可以帮助开发者创建和处理事件,使得代码更加模块化和可复用。在Node.js中,很多模块都使用了事件机制,例如http模块、fs模块等。

下面是一个示例,演示了如何使用事件模块创建和触发事件:

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('触发事件');
});
myEmitter.emit('event');

上面的代码定义了一个继承自EventEmitter的类,该类可以创建事件并触发事件。在上面的代码中,首先定义了一个事件处理函数,然后将该事件处理函数绑定到事件上。最后,通过emit方法触发事件,从而执行事件处理函数。

util模块

util模块提供了一些实用的函数,例如继承、类型判断等。下面是一些常用的util函数:

  • util.inherits(constructor, superConstructor):实现对象之间的继承关系。
  • util.isArray(object):判断对象是否为数组。
  • util.isDate(object):判断对象是否为日期对象。
  • util.isError(object):判断对象是否为错误对象。
  • util.isFunction(object):判断对象是否为函数。
  • util.isNumber(object):判断对象是否为数字。
  • util.isObject(object):判断对象是否为对象。
  • util.isRegExp(object):判断对象是否为正则表达式。
  • util.isString(object):判断对象是否为字符串。
  • util.isUndefined(object):判断对象是否为undefined

下面是一个使用util模块的示例,演示了如何使用inherits函数实现对象之间的继承关系:

const util = require('util');

function Animal(name) {
  this.name = name;
}

Animal.prototype.say = function() {
  console.log('I am ' + this.name);
}

function Cat(name) {
  Animal.call(this, name);
}

util.inherits(Cat, Animal);

const cat = new Cat('Kitty');
cat.say(); // I am Kitty

在上面的示例中,我们定义了一个Animal类和一个Cat类,Cat类从Animal类继承。为了实现这种继承关系,我们使用了Node.js内置的util.inherits()函数。该函数接受两个参数:子类构造函数和父类构造函数,将子类的原型链连接到父类的原型链上。

需要注意的是,util.inherits()函数只会继承父类的原型方法,而不会继承父类的实例属性和方法。所以,在子类的构造函数中,我们需要调用父类的构造函数来初始化子类的实例属性和方法。在示例中,我们使用了Animal.call(this, name)来实现这一点。

最后,我们创建了一个Cat类的实例,调用其say()方法,发现输出了I am Kitty。这说明Cat类已经成功继承了Animal类的原型方法。

querystring模块

Node.js 中,querystring 模块用于解析格式化 URL 查询字符串。 URL 查询字符串是 URL 中包含的键值对。例如,在以下 URL 中,查询字符串为 ?name=john&age=30

https://example.com/?name=john&age=30

querystring 模块提供了以下两个方法:

  1. querystring.parse(str[, sep[, eq[, options]]])

    此方法将 URL 查询字符串解析为一个对象。

    参数:

    • str:要解析的 URL 查询字符串。

    • sep:用于分隔查询字符串中的键值对的字符。默认为 '&'

    • eq:用于分隔键和值的字符。默认为 '='

    • options:一个对象,包含以下属性:

      • decodeURIComponent:用于解码查询字符串中的百分号编码字符的函数。默认为全局的 decodeURIComponent 函数。

    返回值:解析后的对象。

    示例:

    const querystring = require('querystring');

    const qs = 'name=john&age=30';
    const obj = querystring.parse(qs);

    console.log(obj); // { name: 'john', age: '30' }
  1. querystring.stringify(obj[, sep[, eq[, options]]])

    此方法将一个对象序列化为 URL 查询字符串。

    参数:

    • obj:要序列化的对象。

    • sep:用于分隔查询字符串中的键值对的字符。默认为 '&'

    • eq:用于分隔键和值的字符。默认为 '='

    • options:一个对象,包含以下属性:

      • encodeURIComponent:用于编码查询字符串中的特殊字符的函数。默认为全局的 encodeURIComponent 函数。

    返回值:序列化后的 URL 查询字符串。

    示例:

    const querystring = require('querystring');

    const obj = { name: 'john', age: 30 };
    const qs = querystring.stringify(obj);

    console.log(qs); // 'name=john&age=30'

核心API

Buffer API

  • Buffer APINode.js提供的用于处理二进制数据的API。它允许你创建和操作二进制数据,比如读写网络流或文件,处理图像或视频等。

  • Buffer类和Buffer对象是Buffer API的核心。

  • Buffer类用于创建Buffer对象,而Buffer对象是实际存储数据的实例。

  • Buffer类是一个全局类,因此不需要使用require()来加载它。

  • Node.js的代码中,Buffer类可以直接使用。

Buffer编码和解码是将文本数据转换为二进制数据和将二进制数据转换为文本数据的过程。在Node.js中,可以使用Buffer API来进行编码和解码。常见的编码格式有ASCIIUTF-8Base64等,而常见的解码格式有HexUTF-8等。

Buffer的常用方法包括:

  • Buffer.alloc(size[, fill[, encoding]]):创建一个指定大小的Buffer对象,fill为填充值,默认为0
  • Buffer.from(string[, encoding]):将一个字符串转换为Buffer对象。
  • buf.toString([encoding[, start[, end]]]):将Buffer对象转换为字符串,startend参数用于指定转换的起始和结束位置。
  • buf.write(string[, offset[, length]][, encoding]):向Buffer对象中写入数据。
  • buf.slice([start[, end]]):返回一个新的Buffer对象,包含原始Buffer对象指定的部分。
  • buf.length:返回Buffer`对象的长度。
  • 演示:
// 创建一个长度为10的Buffer对象
const buf1 = Buffer.alloc(10);

// 向buf1写入数据
buf1.write('Hello');

// 创建一个长度为5的Buffer对象,包含ASCII编码为'World'的数据
const buf2 = Buffer.from('World', 'ascii');

// 将buf1和buf2连接为一个新的Buffer对象
const buf3 = Buffer.concat([buf1, buf2]);

// 输出buf3的字符串表示形式
console.log(buf3.toString());

// 从buf3中读取数据
console.log(buf3.slice(0, 5).toString()); // 输出'Hello'
console.log(buf3.slice(5).toString()); // 输出'World'

  • 应用:

使用Buffer API来读写文件和比较Buffer对象。首先,我们使用fs模块的readFile()方法将文件内容读取到Buffer对象中,然后使用toString()方法将其转换为字符串并输出。接下来,我们创建一个新的Buffer对象并使用write()方法向其写入数据。最后,我们使用compare()方法比较两个Buffer对象的内容,并根据比较结果输出不同的信息。

const fs = require('fs');
const buffer = Buffer.alloc(1024);

// 读取文件内容到Buffer中
fs.readFile('file.txt', (err, data) => {
  if (err) throw err;
  console.log(`File contents: ${data}`);
});

// 向Buffer中写入数据
buffer.write('Hello World!', 'utf-8');
console.log(`Buffer contents: ${buffer.toString()}`);

// 比较两个Buffer对象
const buffer1 = Buffer.from('Hello');
const buffer2 = Buffer.from('World');
const result = Buffer.compare(buffer1, buffer2);

if (result < 0) {
  console.log(`${buffer1} comes before ${buffer2}`);
} else if (result > 0) {
  console.log(`${buffer1} comes after ${buffer2}`);
} else {
  console.log(`${buffer1} is equal to ${buffer2}`);
}

Stream API

Stream APINode.js提供的用于处理流数据的API。 它允许你在不占用太多内存的情况下,按需处理数据流,比如读取文件流、解析HTTP请求、发送HTTP响应等。

Stream的基本概念包括:可读流(Readable)可写流(Writable)可读写流(Duplex)转换流(Transform)等。

  • 可读流(Readable):可以从中读取数据的流。

  • 可写流(Writable):可以向其中写入数据的流。

  • 可读写流(Duplex):既可以从中读取数据,也可以向其中写入数据的流。

  • 转换流(Transform):可以将可读流转换为可写流的流。

Stream的常用API包括:

  1. fs.createReadStream(path[, options]):创建一个可读流。
  2. fs.createWriteStream(path[, options]):创建一个可写流。
  3. stream.pipe(destination[, options]):将源流中的数据传输到目标流中。
  4. stream.on(event, listener):注册事件监听器。

EventEmitter API

EventEmitter APINode.js提供的事件机制API。它允许你在对象中注册事件,当事件触发时执行回调函数。

EventEmitter类和对象是EventEmitter API的核心。EventEmitter类用于创建具有事件功能的对象,而EventEmitter对象则是一个实例,可以注册和触发事件。

EventEmitter的常用API包括:

  • EventEmitter.on(eventName, listener):注册事件监听器。
  • EventEmitter.emit(eventName[, ...args]):触发事件。
  • EventEmitter.once(eventName, listener):注册一次性事件监听器。
  • EventEmitter.removeListener(eventName, listener):移除事件监听器。

Promise API

Promise是一种在异步编程中处理回调函数的方式。它是一种代表着异步操作结果的对象,并且可以通过一系列的方法链式调用来组织异步操作的处理流程,以实现更加清晰、易于维护的异步代码。

在Node.js中,Promise对象可以通过全局的Promise构造函数来创建。Promise对象有三种状态:pendingfulfilledrejected。在创建Promise对象时,它的状态为pending,表示异步操作正在进行中。当异步操作完成后,Promise对象的状态将变为fulfilledrejected,表示异步操作成功或失败。

Promise对象可以通过以下方法来实现异步操作的处理:

  • then(onFulfilled, onRejected):当Promise对象的状态变为fulfilled时,调用onFulfilled函数来处理异步操作的结果;当Promise对象的状态变为rejected时,调用onRejected函数来处理异步操作的错误。
  • catch(onRejected):当Promise对象的状态变为rejected时,调用onRejected函数来处理异步操作的错误。
  • finally(onFinally):无论Promise对象的状态是什么,都会调用onFinally函数来执行某些清理操作。

除了上述方法外,还有一些其他的静态方法和实例方法,例如:

  • Promise.all(iterable):接收一个可迭代对象(比如数组),并返回一个新的Promise对象。当可迭代对象中的所有Promise对象都变为fulfilled状态时,返回的Promise对象的状态也会变为fulfilled,并将所有Promise对象的结果封装成一个数组作为返回值;如果可迭代对象中有任意一个Promise对象变为rejected状态,则返回的Promise对象的状态也会变为rejected,并将第一个出现的rejected错误作为返回值。
  • Promise.race(iterable):接收一个可迭代对象,返回一个新的Promise对象。当可迭代对象中的任意一个Promise对象变为fulfilledrejected状态时,返回的Promise对象的状态也会相应地变为fulfilledrejected,并将第一个完成的Promise对象的结果作为返回值。
  • Promise.reject(reason):返回一个状态为rejected的Promise对象,并将指定的原因作为错误信息。
  • Promise.resolve(value):返回一个状态为fulfilled的Promise对象,并将指定的值作为返回值。

Timers API

Timers APINode.js的核心API之一,用于实现计时器和定时器的功能。它提供了一组函数和类来支持各种定时器操作,例如延时执行、周期性执行和超时执行等。这些操作都是基于事件循环机制实现的。

Timers API支持两种类型的定时器:立即定时器和延迟定时器。

立即定时器用于在下一次事件循环时执行回调函数,而延迟定时器用于在指定的时间后执行回调函数。另外,Timers API还支持周期性执行定时器,即在指定的时间间隔内重复执行回调函数。

常用的Timers API包括:

  • setTimeout(callback, delay[, ...args]): 延迟指定的毫秒数后执行回调函数。
  • setInterval(callback, delay[, ...args]): 每隔指定的毫秒数执行一次回调函数。
  • setImmediate(callback[, ...args]): 在下一次事件循环时执行回调函数。
  • clearTimeout(timeoutObject): 取消通过setTimeout()函数创建的定时器。
  • clearInterval(intervalObject): 取消通过setInterval()函数创建的定时器。
  • clearImmediate(immediateObject): 取消通过setImmediate()函数创建的定时器。

举个例子,下面的代码演示了如何使用setTimeout()函数实现一个简单的延迟执行操作:

setTimeout(() => {
  console.log('Hello, world!');
}, 1000);

上述代码会在延迟1秒后输出"Hello, world!"。

除了基本的延迟执行操作,Timers API还支持一些高级特性,例如通过unref()函数取消定时器的默认行为,或者通过ref()函数恢复定时器的默认行为。这些特性可以帮助我们更加灵活地控制定时器的行为。

当一个定时器被创建时,它默认会绑定到Node.js的事件循环中,即使在程序的其他部分已经没有需要继续执行的任务,定时器依然会让Node.js的事件循环运行,这可能会导致一些性能问题。因此,我们可以使用unref()函数取消定时器的默认行为,使得该定时器不会阻止Node.js的事件循环退出。

下面是一个示例代码:

const timer = setTimeout(() => {
  console.log('Hello, world!');
}, 1000);

// 取消定时器的默认行为
timer.unref();

在上面的代码中,我们创建了一个1秒后执行的定时器,并使用unref()函数取消了其默认行为。这意味着,即使该定时器的回调函数还没有被执行,事件循环也可以在没有其他任务的情况下退出。

如果之后需要重新启用定时器的默认行为,可以使用ref()函数进行恢复:

// 恢复定时器的默认行为
timer.ref();

使用ref()函数之后,定时器将重新绑定到事件循环中,即使没有其他任务需要执行,事件循环也会等待定时器的回调函数执行完成。

扩展 为什么不用clearTimeout()clearInterval(),而用unref呢?

调用 clearTimeout()clearInterval() 函数可以取消定时器并停止定时器任务的执行。这样可以避免定时器一直执行造成的性能问题。但是,有时候我们希望定时器在某个时间点执行,但是不想让它一直占用系统资源直到执行完成。这时候,我们可以调用 unref() 函数来取消定时器的默认行为,让定时器在任务执行完后不再阻止进程的退出,从而提高应用程序的性能。

需要注意的是,调用 unref() 函数并不会取消定时器本身,只是取消了其对 Node.js 进程退出的影响。如果想要完全取消定时器,仍然需要调用 clearTimeout()clearInterval() 函数。

总结

本文介绍了,Nodejs的核心模块与核心API,熟知这一些,才算是对Nodejs有一个初步的了解,下一章 >>>> NodeJS探索系列(三) -- Node.js 的模块化开发