Node.js是开源、跨平台的JavaScript运行时环境,构建在Chrome
的v8
引擎之上。
Node.js是异步事件驱动
的单线程
模型。
由于Node.js是异步非阻塞
的特性,因此适用于I/O密集型
应用场景。
需要注意的是,Node.js是单线程模型,需要避免CPU的耗时操作。
Node.js版本的特点:
- Current 版本:包含了主分支上非重大的更新。
- Active LTS 版本:经过 团队审核的新功能、错误修复和更新,已被确定为适用于发布线且稳
定。 - Maintenance 版本:关键错误修复和安全更新。
建议: 由于偶数版本获得的支持时间比较⻓,推荐在生产环境中使用偶数
版本的 Node.js 。
一、CommonJs模块
Node.js
应用由模块组成,默认采用的是 CommonJS
规范。即每个文件是一个模块,有自己的作 用域。在一个文件里面定义的变量、函数、类等都是私有的,对其它文件不可⻅。
不过我们可以通过在模块中通过 exports
或者 module.exports
命令将内容导出,其它文件就可 以通过 require
命令访问到这些被导出的内容。
- module: 是对当前模块对象的引用。 module.exports 用于定义模块导出的内容。
- exports: 是 对应的引用。
- require: 加载模块,访问模块导出的内容,多次加载模块使用缓存数据,第二次加载时直接返回上一次的缓存结果。
- __dirname: 当前模块的文件夹路径
- __filename: 当前模块的文件路径。
- require.cache:存储已加载的模块的缓存对象
- require.main:主入口文件模块对象
- require.resolve:返回模块解析后的文件路径
Node.js会检测出当前代码是否存在循环引用
,并做相应的处理,如抛出warning
二、ECMAScript模块
ECMAScript Module( ESM
)是打包Javascript代码以供重复使用的官方标准格式。模块是使用import
和 export
相关的语句定义的。
ECMAScript和CommonJs区别
- ESM没有 require 、 exports 、 module.exports ,使用的是import、export
- ESM没有 __filename 、 __dirname 。
- ESM没有 require.resolve ,不过可以使用 import.meta.resolve 。
- ESM没有 require.cache 。
三、API
1、module.builtinModules
用于验证是否是由第三方维护
const builtin = require('node:module').builtinModules;
2、module.createRequire(filename)
创建 require 函数。 参数必须是URL对象、 字符串,或者是绝对路径。
3、module.isBuiltin(moduleName)
检测模块是否是内置模块。
const isBuiltin = require('node:module').isBuiltin;
console.log(isBuiltin('node:path')); // true
console.log(isBuiltin('path')); // true
console.log(isBuiltin('wss')); // false
4、URL模块
URL模块提供了一些非常实用的用于解析 的方法。
1. new URL()
const url = require('node:url');
const myURL =
new URL('https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash')
/*
href: 'https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash',
origin: 'https://sub.example.com:8080',
protocol: 'https:',
username: 'user',
password: 'pass',
host: 'sub.example.com:8080',
hostname: 'sub.example.com',
port: '8080',
pathname: '/p/a/t/h',
search: '?query=string',
searchParams: URLSearchParams { 'query' => 'string' },
hash: '#hash'
}
*/
console.log(myURL);
修改URL信息
const url = require('node:url');
const myURL =
new URL('https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash')
myURL.port = 8081;
// https://user:pass@sub.example.com:8081/p/a/t/h?query=string#hash 8
console.log(myURL.toString());
2. searchParams
用于读取和写入URL的query信息
const myURL = new URL('https://example.org/?abc=123');
// 123
console.log(myURL.searchParams.get('abc'));
myURL.searchParams.append('abc', 'xyz');
// https://example.org/?abc=123&abc=xyz
console.log(myURL.href);
myURL.searchParams.delete('abc');
myURL.searchParams.set('a', 'b');
// false
console.log(myURL.searchParams.has('abc'));
// https://example.org/?a=b
console.log(myURL.href);
myURL.search = new URLSearchParams('name=lily');
// https://example.org/?name=lily
console.log(myURL.href);
手动创建URLSearchParams对象
const newSearchParams = new URLSearchParams('a=b');
newSearchParams.append('a', 'c');
// a=b&a=c
console.log(newSearchParams.toString());
5、querystring
querystring模块提供了一些非常实用的用于解析查询字符串的方法。
1. querystring.parse()解析查询字符串
/*
{ a: 'b', c: [ 'd', 'f' ] }
*/
const querystring = require('node:querystring');
const obj = querystring.parse('a=b&c=d&c=f');
console.log(obj);
2. querystring.stringify()将对象序列化成查询字符串
const querystring = require('node:querystring'); 2
const obj = {
a: 'b',
c: ['d', 'f']
};
// a=b&c=d&c=f
console.log(querystring.stringify(obj));
// a:b;c:d;c:f
console.log(querystring.stringify(obj, ';', ':'));
6、Path模块
Path模块提供了用于处理文件和目录路径的实用程序。
1. path.basename(path, suffix)返回路径的最后一部分。
const path = require('node:path'); 2
// index.html
path.basename('/foo/bar/baz/asdf/index.html');
// index
path.basename('/foo/bar/baz/asdf/index.html', '.html')
2. path.delimiter 返回特定平台的路径之间的分隔符。Windows使用的是‘;’,POSIX使用的是‘;’
const path = require('node:path');
console.log(path.delimiter);
console.log(process.env.PATH);
3. path.dirname(path)返回的是路径的目录名
const path = require('node:path'); 2
3 // /foo/bar/baz/asdf
4 path.dirname('/foo/bar/baz/asdf/quux');
4. path.extname(path)返回路径扩展名
根据最后一个 . 字符到路径结尾的字符串
const path = require('node:path');
// .html
path.extname('index.html');
// .
path.extname('index.');
//
path.extname('index');
// .md
path.extname('.index.md');
5. path.isAbsoute(path)判断是否是绝对路径
如果path是绝对路径,则返回true
const path = require('node:path');
ath.isAbsolute('/foo/bar'); // true
6. path.join([…paths])连接给定路径并format
const path = require('node:path'); 2
// /foo/bar/baz/asdf
path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
7. path.normalize(path) 规范化给定路径
const path = require('node:path');
// /foo/bar/baz/asdf
path.normalize('/foo/bar//baz/asdf/quux/..');
8. path.relative(from, to) 从a到b的相对路径
根据当前工作目录返回从from到to的相对路径
const path = require('node:path'); 2
// ../../impl/bbb
path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb');
9. path.resolve([…paths])将一些路径解析成绝对路径
const path = require('node:path'); 2
// /foo/bar/baz
path.resolve('/foo/bar', './baz');
10. path.sep返回特定平台的路径中目录之间分隔符
const path = require('node:path');
// windows使用的是\,POSIX使用的是/
console.log(path.sep);
7、File模块
1. 创建文件夹mkdirSync
const { mkdirSync } = require('node:fs');
try {
mkdirSync('./temp');
// 不可以同时创建不存在的多层级目录 mkdirSync('./logs/errorlog');
} catch (err) {
// handle the error
console.log(err);
}
2. 写入文件writeFileSync(如果文件不存在,则创建文件)
const { writeFileSync } = require('node:fs');
const path = require('node:path');
try {
writeFileSync(path.resolve(__dirname, './music.txt'), '新的内容', 'utf8'))
} catch (err) {
// handle the error
}
3. 追加文件内容appendFileSync
const { appendFileSync } = require('node:fs');
const path = require('node:path');
try {
appendFileSync(path.resolve(__dirname, './music.txt'), '新的内容88888', 'utf8'))
} catch (err) {
// handle the error
}
4. 读取文件夹readdirSync
const { readdirSync } = require('node:fs');
try {
const files = readdirSync('.');
console.log(files);
} catch (err) {
// handle the error
console.log(err);
5. 读取文件readFileSync
const { readFileSync } = require('node:fs');
const path = require('node:path');
try {
const tent = readFileSync(path.resolve(__dirname, './music.txt'), '新的内容88888', 'utf8'))
console.log(tent)
} catch (err) {
// handle the error
}
6. statSync区分文件夹还是文件、判断文件或者文件夹是否存在
const { statSync } = require('node:fs');
try {
const stat = statSync('./temp');
if (stat.isDirectory()) {
console.log('这是一个文件夹');
} else if (stat.isFile()) {
console.log('这是一个文件');
} catch (err) {
// handle the error
console.log(err && '文件不存在');
}
7. renameSync重命名文件
const { renameSync } = require('node:fs');
try {
renameSync('./music.txt', './new-music.txt');
} catch (err) {
// handle the error
}
8. rmdirSync删除文件夹
const { rmdirSync } = require('node:fs');
try {
// 目录不为空,则删除失败
rmdirSync('./temp');
} catch (err) {
// handle the error
console.log(err);
}
9. unlinkSync删除文件
const { unlinkSync } = require('node:fs');
try {
unlinkSync('./temp');
} catch (err) {
// handle the error
console.log(err);
}
10. 实战:递归创建文件夹
const {mkdirSync, statSync } = require('node:fs');
const path = require('node:path');
function mkdirs(dir){
const dirs = dir.split(path.sep);
dirs.forEach((current, index)=>{
const dirPath = dirs.slice(0, index + 1).join(path.sep);
if(current){
let isExisted = false;
try {
isExisted = statSync(dirPath).isDirectory();
} catch (err) {
// 说明文件不存在
}
if (!isExisted) {
mkdirSync(dirPath)
}
}
})
}
mkdirs(path.resolve(__dirname, './temp/logs'));
8、process模块
process 对象提供有关当前 Node.js 进程的信息。
1. cwd()返回进程的当前工作目录
const { cwd } = require('node:process');
// /xxxx/Node核心模块使用/10.process
console.log(cwd());
2. chdir()更改当前工作目录
const { chdir, cwd } = require('node:process');
// Starting directory: /xxxx/Node核心模块使用/10.process
console.log(`Starting directory: ${cwd()}`);
try {
chdir('../');
// New directory: /xxxx/Node核心模块使用
console.log(`New directory: ${cwd()}`);
} catch (err) {
console.error(`chdir: ${err}`);
}
3. process.env 环境变量
const process = require('node:process');
console.log(process.env);
process.env.mode = 'development';
console.log(process.env);
9、Events模块
Node.js的大部分核心API都是围绕着一种异步事件驱动架构构建的。在这种架构中,某些类型的对象(称为“ emitters ”)会发出特定的具名事件,因此导致调用Function对象(“listeners”)
1. on/emit基本用法
const EventEmitter = require('node:events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('an event occurred!');
});
myEmitter.emit('event');
2. 传递参数和 listener中的this
const EventEmitter = require('node:events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', function (a, b) {
// a, b, true
console.log(a, b, this === myEmitter);
});
myEmitter.emit('event', 'a', 'b');
3. 只触发一次的事件函数
const EventEmitter = require('node:events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
letm=0;
myEmitter.once('event', () => {
console.log(++m);
});
// 1
myEmitter.emit('event');
// 没有反应
myEmitter.emit('event');
4. 绑定多个listeners
eventEmitter.listeners返回函数列表。
const EventEmitter = require('node:events');
const myEmitter = new EventEmitter();
myEmitter.on('event', function firstListener() {
console.log('Helloooo! first listener');
});
myEmitter.on('event', function secondListener(arg1, arg2) {
console.log(`event with parameters ${arg1}, ${arg2} in second listener`);
});
myEmitter.on('event', function thirdListener(...args) {
const parameters = args.join(', ');
console.log(`event with parameters ${parameters} in third listener`);
});
console.log(myEmitter.listeners('event'));
myEmitter.emit('event', 1, 2, 3, 4, 5);
5. 返回所有的事件名称
eventEmitter.eventNames返回所有的事件名称列表。
onst EventEmitter = require('node:events');
const myEE = new EventEmitter();
myEE.on('foo', () => {});
myEE.on('bar', () => {});
// ['foo', 'bar']
console.log(myEE.eventNames());
6. 移除listener
eventEmitter.off是eventEmitter.removeListener 的别名。
const EventEmitter = require('node:events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
const callbackA = () => {
console.log('A');
myEmitter.removeListener('event', callbackB);
};
const callbackB = () => {
console.log('B');
};
myEmitter.on('event', callbackA);
myEmitter.on('event', callbackB);
// A、B
myEmitter.emit('event');
// A
myEmitter.emit('event');
7. 移除所有的listener
eventEmitter.removeAllListeners 移除指定的事件所有绑定过的回调函数。
const EventEmitter = require('node:events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
const callbackA = () => {
console.log('A');
};
const callbackB = () => {
console.log('B');
};
myEmitter.on('event', callbackA);
myEmitter.on('event', callbackB);
// A、B
myEmitter.emit('event');
myEmitter.removeAllListeners('event');
// 没有反应
myEmitter.emit('event');
10、Stream模块
Stream
是一个抽象接口,用于处理Node.js中的流数据。node:stream模块为实现流接口提供了API。
Stream可以是可读的,也可以是可写的,或者两者都有。所有流都是EventEmitter
的实例。
Node.js提供了很多流对象,比如操作文件系统、对HTTP服务的请求等。
通常在处理大体积文件的时候用到流,防止占用过多的内存。
1. 创建文件只读流
// 创建只读流
const { createReadStream, statSync } = require('node:fs');
// highWaterMark
const stream = createReadStream('./demo.txt', { encoding: 'utf8' });
let totalLength = 0;
stream.on('data', (chunk) => {
totalLength += Buffer.byteLength(chunk);
});
stream.on('end', () => {
console.log(`数据读取完毕`);
console.log(statSync('./demo.txt').size);
console.log(totalLength);
});
2. 创建文件可写流
// 复制文件
const { createReadStream, createWriteStream, statSync } = require('node:fs');
const readStream = createReadStream('./demo.txt', { encoding: 'utf8' });
const writeStream = createWriteStream('./demo-write.txt', { encoding: 'utf8' });
// 方式1:手动写入
readStream.on('data', (chunk) => {
writeStream.write(chunk);
});
readStream.on('end', () => {
console.log(`数据写入完毕`);
console.log(statSync('./demo.txt').size === statSync('./demo-write.txt').size);
});
// 方式2:通过管道完成写入
readStream
.on('end', () => {
console.log(`数据读取完毕`);
})
.pipe(writeStream)
.on('finish', () => {
console.log(`写入完毕`);
console.log(statSync('./demo.txt').size === statSync('./demo-write.txt').size);
})
3. 转换流
// 压缩文件
const { createReadStream, createWriteStream } = require('node:fs');
const zlib = require('node:zlib');
const readStream = createReadStream('./demo.txt');
const duplexStream = zlib.createGzip();
const writeStream = createWriteStream('./demo.tar.gz');
readStream.pipe(duplexStream).pipe(writeStream);
4. 下载文件
const { createReadStream } = require('node:fs');
const zlib = require('node:zlib');
const http = require('node:http');
http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Content-Encoding': 'gzip',
'Content-Disposition': 'attachment; filename=demo.tar.gz',
});
const readStream = createReadStream('./demo.txt');
const duplexStream = zlib.createGzip();
readStream.pipe(duplexStream).pipe(res);
}).listen(8890);
5. readline模块
node:readline模块提供了一个接口,用于一次一行地从Readable流读取数据。
const readline = require('node:readline');
const { stdin: input, stdout: output } = require('node:process');
const rl = readline.createInterface({ input, output });
rl.question('What do you think of Node.js? ', (answer) => {
console.log(answer);
rl.close();
});
逐行读取文件内容
const { createInterface } = require('node:readline');
const { once } = require('node:events');
const { createReadStream } = require('node:fs');
(async () => {
const rl = createInterface({
input: createReadStream('./demo.txt'),
});
let line = 0;
rl.on('line', (lineContent) => {
console.log(`${++line}:`, lineContent.toString());
});
await once(rl, 'close');
console.log('File read complete.');
})();
11、 HTTP模块
http模块是一个非常常用的模块,我们可以用它来搭建一个服务器,或者使用它发送请求。
1. 搭建服务器
const http = require('node:http');
const { Buffer } = require('node:buffer');
const { URL } = require('node:url');
const querystring = require('node:querystring');
// 程序支持的HTTP方法列表
// console.log(http.METHODS);
// 所有标准HTTP响应状态代码的集合,以及每个状态代码的简短描述。
// console.log(http.STATUS_CODES);
const server = http.createServer((req, res) => {
/* --------- Request ---------- */
// console.log(req.headers);
// console.log(req.headers['cookie']);
// console.log(req.httpVersion);
// console.log(req.method);
// console.log(req.url);
// GET请求
// console.log('query:', new URL(req.url, 'http://localhost:8888').searchParams);
/* --------- Response ---------- */
// 状态码和状态码描述
// 获取状态码
// console.log(res.statusCode);
// console.log(res.statusMessage);
// 设置状态码
// res.statusCode = 404;
// res.statusMessage = 'not found';
// 设置响应头信息
res.setHeader('Content-Type', 'text/html');
res.setHeader('X-Foo', 'Bar');
// res.writeHead(status, statusMessage, headers)
// res.writeHead(200, 'Very OK', { 'Content-Type': 'text/plain' });
// 获取响应头信息(header name自动转换成小写)
// console.log(res.getHeaders());
// console.log(res.getHeaderNames());
// console.log(res.getHeader('X-Foo'));
// console.log(res.hasHeader('X-Foo'));
// 响应主体信息
// res.write(chunk: string | buffer, encoding, callback)
res.write(Buffer.from('hello world'))
res.write('ok 1');
res.write('ok 2');
// res.end(data, encoding, callback); 相当于 res.write(data, encoding) + res.end(callback)
res.end('ok end', 'utf8', () => {
console.log('数据发送完成');
});
});
// 启动服务
server.listen(8888, 'localhost', () => {
console.log('服务启动成功');
});
// 关闭服务
// server.on('close', () => {
// console.log('The server is closed');
// });
// server.close(() => {
// console.log('Close the server.');
// });
2. 获取POST请求的数据
const http = require('node:http');
const querystring = require('node:querystring');
const server = http.createServer((req, res) => {
if (req.method === 'POST') {
let postData = '';
req.on('data', (chunk) => {
postData += chunk.toString();
});
req.on('end', () => {
console.log('postData:', querystring.parse(postData));
res.end('POST request end');
});
} else {
res.end('Other request end');
}
});
server.listen(8888, 'localhost', () => {
console.log('服务启动成功');
});
3. 客户端:发送GET网络请求
// http-server.js
const http = require('node:http');
const server = http.createServer((req, res) => {
if (req.method === 'GET') {
console.log(req.headers['x-auth']);
res.setHeader('Content-Type', 'text/html');
res.end('This is a get request');
} else if (req.method === 'POST') {
// 接收client发送过来的数据
let postData = '';
req.on('data', (chunk) => {
postData += chunk.toString();
});
req.on('end', () => {
console.log('postData:', postData);
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ a: 'b' }));
});
} else {
res.statusCode = 404;
res.end();
}
});
server.listen(8889, 'localhost', () => {
console.log('服务启动成功');
});
// 关闭服务
server.on('error', (error) => {
console.log('The server has error', error.message);
})
server.on('close', () => {
console.log('The server is closed');
});
// http-get.js
const http = require('node:http');
// http.get(url, options, callback)
const client = http.get('http://localhost:8889/?a=b', {
headers: {
'X-AUTH': '1234',
},
}, (res) => {
console.log(res.statusCode);
console.log(res.headers);
res.setEncoding('utf8');
// 获取返回的内容
let rawData = '';
res.on('data', (chunk) => {
rawData += chunk;
});
res.on('end', () => {
console.log('data:', rawData);
});
});
// 请求服务出现错误
client.on('error', (e) => {
console.log('client error info', e.message);
});
// 关闭客户端
client.end();
4. 客户端:发送POST网络请求
// http-post.js
const http = require('node:http');
const { Buffer } = require('node:buffer');
const postData = JSON.stringify({
'msg': 'Hello World!',
});
// http.request(options, callback)
const client = http.request({
protocol: 'http:',
hostname: 'localhost',
port: '8889',
pathname: '/',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData),
},
}, (res) => {
console.log(res.statusCode);
console.log(res.headers);
res.setEncoding('utf8');
// 获取返回的内容
let rawData = '';
res.on('data', (chunk) => {
rawData += chunk;
});
res.on('end', () => {
console.log('data:', rawData);
});
});
// 请求服务出现错误
client.on('error', (e) => {
console.log('client error info', e.message);
});
client.write(postData);
// 关闭客户端
client.end();