node/基础使用

学习资料

node菜鸟教程

node 官方api

深入浅出node.js

模块机制

CommonJs规范

Node与浏览器以及W3C组织、CommonJS组织、ECMAScript之间的关系,共同构成了一个繁荣的生态系统。

模块分类

一类是Node提供的模块,称为核心模块;另一类是用户编写的模块,称为文件模块

复制代码
部分核心模块是在node进程启动的时候就已经被加载进内存了。
erlang 复制代码
文件模块则是在运行时动态加载.

模块导入

js 复制代码
// 引入核心模块
require ('math') 
// 引入文件模块
require ('./math.js') 

注意:当引入的模块是文件夹 的时候,node会在这个文件夹里面查看是否有package.json,如果有则查看json里面的main属性所指引的文件,从而加载它,如果没有json文件或者main属性不存在,则去加载该文件夹下的index.js或者index.json

模块导入的工作原理

  1. 将路径转为绝对路径
  2. 判断是否导入的对象是否已经存在于缓存中,如果存在则直接返回缓存,结束
  3. 根据路径获取模块里面的代码
  4. 用函数包裹改代码自动执行,如下
js 复制代码
let module = {}
let exports = module.exports = {}
(function (exports, require, module, __filename, __dirname){
    const test = { name: 'phxxhp'}
    module.exports = test
    
    return module.exports
})(exports, require, module, __filename, __dirname)
  1. 缓存模块执行结果

这里需要注意的点就是:模块里面的代码只会执行一次

模块导出

module代表模块本身,exports是module的属性,exports的功能是将定义的函数或者变量导出。

js 复制代码
exports.add = function(){}

内存控制

Node中通过JavaScript使用内存时就会发现只能使用部分内存(64位系统下约为1.4 GB,32位系统下约为0.7 GB)

查看内存信息

Node中的内存使用并非都是通过V8进行分配的。我们将那些不是通过V8分配的内存称为堆外内存。Buffer对象不同于其他对象,它不经过V8的内存分配机制,所以也不会有堆内存的大小限制。Node的内存构成主要由通过V8进行分配的部分和Node自行分配的部分。受V8的垃圾回收限制的主要是V8的堆内存。

扩大内存的方法

Node在启动时可以传递--max-old-space-size或--max-new-space-size来调整内存限制的大小

arduino 复制代码
node --max-old-space-size=1700 index.js // 设置老生代内存
node --max-new-space-size=1700 index.js // 设置新生代内存

垃圾内存回收算法

现代的垃圾回收算法中按对象的存活时间将内存的垃圾回收进行不同的分代,然后分别对不同分代的内存施以更高效的算法。在V8中,主要将内存分为新生代和老生代两代。新生代中的对象为存活时间较短的对象,(新生代中的对象主要通过Scavenge算法进行垃圾回收)老生代中的对象为存活时间较长或常驻内存的对象(V8在老生代中主要采用了Mark-Sweep和Mark-Compact相结合的方式进行垃圾回收)

如何触发垃圾内存回收

只被局部变量引用的对象存活周期较短,在作用域释放后,局部变量失效,其引用的对象将会在下次垃圾回收时被释放。

全局作用域需要直到进程退出才能释放,此时将导致引用的对象常驻内存(常驻在老生代中)。如果需要释放常驻内存的对象,可以通过delete操作来删除引用关系。或者将变量重新赋值,让旧的对象脱离引用关系。在接下来的老生代内存清除和整理的过程中,会被回收释放。在V8中通过delete删除对象的属性有可能干扰V8的优化,所以通过赋值方式(undefined or null)解除引用更好。

在JavaScript中,实现外部作用域访问内部作用域中变量的方法叫做闭包(closure)。闭包是JavaScript的高级特性,利用它可以产生很多巧妙的效果。它的问题在于,一旦有变量引用这个中间函数,这个中间函数将不会释放,同时也会使原始的作用域不会得到释放,作用域中产生的内存占用也不会得到释放。除非不再有引用,才会逐步释放。

内存泄漏的原因与解决方法

造成内存泄漏的原因有如下几个:缓存,队列消费不及时,作用域未释放。

在Node中,任何试图拿内存当缓存的行为都应当被限制。当然,这种限制并不是不允许使用的意思,而是要小心为之(LRU算法)。

如何使用大量缓存,目前比较好的解决方案是采用进程外的缓存,进程自身不存储状态。

由于模块的缓存机制,模块是常驻老生代的。在设计模块时,要十分小心内存泄漏的出现。

在JavaScript中可以通过队列(数组对象)来完成许多特殊的需求,队列在消费者-生产者模型中经常充当中间产物。这是一个容易忽略的情况,因为在大多数应用场景下,消费的速度远远大于生产的速度,内存泄漏不易产生。但是一旦消费速度低于生产速度,将会形成堆积。深度的解决方案应该是监控队列的长度,一旦堆积,应当通过监控系统产生报警并通知相关人员。另一个解决方案是任意异步调用都应该包含超时机制,一旦在限定的时间内未完成响应,通过回调函数传递超时异常,使得任意异步调用的回调都具备可控的响应时间,给消费速度一个下限值。对于Bagpipe而言,它提供了超时模式和拒绝模式。启用超时模式时,调用加入到队列中就开始计时,超时就直接响应一个超时错误。启用拒绝模式时,当队列拥塞时,新到来的调用会直接响应拥塞错误。这两种模式都能够有效地防止队列拥塞导致的内存泄漏问题。

读写大文件

由于V8的内存限制,我们无法通过fs.readFile()和fs.writeFile()直接进行大文件的操作,而改用fs.createReadStream()和fs.createWriteStream()方法通过流的方式实现对大文件的操作

理解buffer

网络编程

构建web应用

玩转进程

Node.js EventEmitter

Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。

Node.js 里面的许多对象都会分发事件:

一个 net.Server 对象会在每次有新连接时触发一个事件,

一个 fs.readStream 对象会在文件被打开的时候触发一个事件。

所有这些产生事件的对象都是 events.EventEmitter 的实例。

EventEmitter 类

EventEmitter 的核心就是事件触发与事件监听器功能的封装。

EventEmitter 类的简单使用

js 复制代码
// 引入events模块
var events = require('events');  
// 创建实例
var event = new events.EventEmitter(); 
// 定义监听函数'some_event' --on
event.on('some_event', function() { 
    console.log('some_event 事件触发'); 
}); 

setTimeout(function() { 
    // 触发监听函数  -- emit
    event.emit('some_event'); 
}, 1000); 

EventEmitter 提供了多个属性,如 on 和 emit。on 函数用于绑定事件函数,emit 属性用于触发一个事件。

注意:

大多数时候我们不会直接使用 EventEmitter,而是在对象中继承它。包括 fs、net、 http 在内的,只要是支持事件响应的核心模块都是 EventEmitter 的子类。

为什么要这样做呢?原因有两点:

首先,具有某个实体功能的对象实现事件符合语义, 事件的监听和发生应该是一个对象的方法。

其次 JavaScript 的对象机制是基于原型的,支持 部分多重继承,继承 EventEmitter 不会打乱对象原有的继承关系。

方法与属性以及事件(翻到下面)

Node.js Buffer(缓冲区)

JavaScript 没有二进制数据类型。 因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区 处理像TCP流或文件流.

JS字符串与二进制的相互转换

js 复制代码
// js字符串 以ascii编码 转换成buf二进制。
const buf = Buffer.from('1', 'ascii');

// buf二进制以 16进制编码 转换成js字符串
console.log(buf.toString('hex'));

// buf二进制以 Base64编码 转换成js字符串
console.log(buf.toString('base64'));

// buf二进制以 UTF-8编码 转换成js字符串
console.log(buf.toString('UTF-8'));

支持的转换编码

vbnet 复制代码
ascii - 仅支持 7 位 ASCII 数据。如果设置去掉高位的话,这种编码是非常快的。

utf8 - 多字节编码的 Unicode 字符。许多网页和其他文档格式都使用 UTF-8 。(默认)

utf16le - 2 或 4 个字节,小字节序编码的 Unicode 字符。支持代理对(U+10000 至 U+10FFFF)。

ucs2 - utf16le 的别名。

base64 - Base64 编码。

latin1 - 一种把 Buffer 编码成一字节编码的字符串的方式。

binary - latin1 的别名。

hex - 将每个字节编码为两个十六进制字符。

创建 Buffer 类(翻到下面)

写入与读取缓冲区

js 复制代码
/**
 * 需求
 * 创建buff,写入ascii的字符串,读取buff转换成ascii编码的字符串
 */

// 定义缓冲区有256个字节
let  buffer = Buffer.alloc(256);  // 占用256个字节

// 写入'a'
let len = buffer.write('a',0,buffer.length,'ascii');  // 返回一共写入多少个字节。(注意不同的编码形式,占用的字节不同)
// 写入'bc'
len = buffer.write('bc',len,buffer.length,'ascii');  // 返回一共写入多少个字节。(注意不同的编码形式,占用的字节不同)

// 读 abc
console.log(buffer.toString('ascii'))

Node.js Stream(流)

Stream 是一个抽象接口,Node 中有很多对象实现了这个接口。例如,对http 服务器发起请求的request 对象就是一个 Stream,还有stdout(标准输出)。

所有的 Stream 对象都是 EventEmitter 的实例。常用的事件有:

data - 当有数据可读时触发。

end - 没有更多的数据可读时触发。

error - 在接收和写入过程中发生错误时触发。

finish - 所有数据已被写入到底层系统时触发。

读一个txt文件

js 复制代码
/**
 * 需求,读取同文件下的input.txt
 */
var fs = require("fs");
var data = '';

// 创建可读流
var readerStream = fs.createReadStream('F:/web/node.js/Stream/input.txt');  // 此路径为绝对路径

// 设置编码为 utf8。
readerStream.setEncoding('UTF8');

// 文件读取到一行数据触发
readerStream.on('data', function(chunk) {       // chunk代表读取文件一行数据,每次读取一行数据。
   data += chunk;
});

// 文件读取完毕后触发
readerStream.on('end',function(){
   console.log(data);
});

// 文件读取完过程中出现错误触发
readerStream.on('error', function(err){
   console.log(err.stack);
});

console.log("程序执行完毕");

写一个txt文件

js 复制代码
/**
 * 需求:写入一个txt文件
 */
var fs = require("fs");
var data = '菜鸟教程官网地址:www.runoob.com';

// 创建一个可以写入的流,写入到文件 output.txt 中
var writerStream = fs.createWriteStream('F:/web/node.js/Stream/output.txt'); // 绝对路径,如果文件不存在则创建。

// 使用 utf8 编码开始写入数据
writerStream.write(data,'UTF8');                    // 写入的数据会覆盖原来的所有数据。

// 标记文件末尾
writerStream.end();                                 // 标记了末尾 下面的 finish事件才会触发。

// 处理流事件 --> data, end, and error
writerStream.on('finish', function() {
    console.log("写入完成。");
});

writerStream.on('error', function(err){
   console.log(err.stack);
});

console.log("程序执行完毕");

将input文件的内容拷贝到output文件里面

js 复制代码
/**
 * 需求:
 * 将input文件的内容拷贝到output文件里面
 */
var fs = require("fs");

// 创建一个可读流
var readerStream = fs.createReadStream('F:/web/node.js/Stream/input.txt');

// 创建一个可写流
var writerStream = fs.createWriteStream('F:/web/node.js/Stream/output.txt');

// 管道读写操作
// 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中
readerStream.pipe(writerStream);

console.log("程序执行完毕");

压缩一个文件

js 复制代码
/**
 * 需求:
 * 压缩一个文件
 */
var fs = require("fs");
var zlib = require('zlib');

// 创建一个可读流
var readerStream = fs.createReadStream('F:/web/node.js/Stream/input.txt');

// 创建一个可写流
var writerStream = fs.createWriteStream('F:/web/node.js/Stream/input.txt.gz');

// 链式压缩文件
readerStream.pipe(zlib.createGzip())
            .pipe(writerStream)
  
console.log("文件压缩完成。");

解压一个文件

js 复制代码
/**
 * 需求:
 * 解压一个文件
 */
var fs = require("fs");
var zlib = require('zlib');

// 创建一个可读流
var readerStream = fs.createReadStream('F:/web/node.js/Stream/input.txt.gz');

// 创建一个可写流
var writerStream = fs.createWriteStream('F:/web/node.js/Stream/input.txt');


// 解压 input.txt.gz 文件为 input.txt
readerStream.pipe(zlib.createGunzip())
            .pipe(writerStream);
  
console.log("文件解压完成。");

Node.js 全局对象(属性与方法)

Node.js 中的全局对象是 global,所有全局变量(除了 global 本身以外)都是 global 对象的属性。不同模块都可以访问得到得全局对象。

1、定义全局变量的方法:

global.a = 10;

b = 10; // 未声明直接定义

2、node.js默认定义的全局变量:

__filename 表示当前正在执行的脚本的文件名。

__dirname 表示当前执行脚本所在的目录。 setTimeout(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。

clearTimeout( t ) 全局函数用于停止一个之前通过 setTimeout() 创建的定时器。

setInterval(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。

console 用于提供控制台标准输出

process 它用于描述当前Node.js 进程状态的对象 通常在你写本地命令行程序的时候,少不了要 和它打交道。(建议多看看,翻到下面哦)

fs相关操作

1、文件相关操作

1.1、创建

fs.open

fs.open(path, flags, mode, callback)

  • path - 文件的路径。
  • mode - 设置文件模式(权限),文件创建默认权限为 0666(可读,可写)。
  • callback - 回调函数,带有两个参数如:callback(err, fd)。
js 复制代码
fs.open('./open_w.txt', 'w', (err, fd) => {
    if (err) {
        return console.error(err)
    }
    console.log('fs.open',fd)
    fs.close(fd, () => {
        console.log('fs.close')
    })
})

fs.writeFile

fs.writeFile(path|fd, data, options, callback)

  • file - 文件名或文件描述符。
  • data - 要写入文件的数据,可以是 String(字符串) 或 Buffer(缓冲) 对象。
  • options - 该参数是一个对象,包含 {encoding, mode, flag}。默认编码为 utf8, 模式为 0666 , flag 为 'w'
  • callback - 回调函数,回调函数只包含错误信息参数(err),在写入失败时返回。

writeFile 直接打开文件默认是 w 模式,所以如果文件存在,该方法写入的内容会覆盖旧的文件内容。

js 复制代码
fs.writeFile('./open_w.txt', '你好', (err) => {
    if (err) {
        return console.error(err)
    }
    console.log('writeFile: 写入成功')
})

1.2、删除

fs.ftruncate 删文件部分内容

fs.ftruncate(fd, len, callback)

  • fd 文件操作符(通过fs.open获取)
  • len 文件内容截取的长度,截取的内容是【0, len)

保留len个字节内容,其他的全部删除。

js 复制代码
fs.open('./2.txt','r+', (err, fd) => {
    if (err) {
        console.log("fs.ftruncate error");
        return
    }
    // UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。 现在'./2.txt'的内容为"a中小城镇下" 
    // 我想截取"a中"
    // 那么len = a(1) + 中(3) = 4
    const len = 4
    fs.ftruncate(fd, len, (err) => {
        if (err){
            return console.log(err);
        }
        console.log("fs.ftruncate", len);
        fs.close(fd, () => {
            console.log('fs.close')
        })
    });
})
js 复制代码
fs.unlink('./writeFile_1706758883155.txt',(err) => {
    if (err) {
        return console.error(err);
    }
    console.log('fs.unlink')
})

1.3、读取

fs.readFile 读整个文件内容

fs.readFile(path, callback) callback(err, data) data为buffer

js 复制代码
fs.readFile('./1.txt', (err, data) => {
    if (err) {
        return console.error(err)
    }
    console.log('fs.readFile', data.toString())
})

fs.read 读文件的部分内容

fs.read(fd, buffer, offset, length, position, callback)

  • fd - 通过 fs.open() 方法返回的文件描述符。
  • buffer - 数据写入的缓冲区。
  • offset - 缓冲区写入的写入偏移量。
  • length - 要从文件中读取的字节数。
  • position - 文件读取的起始位置,如果 position 的值为 null,则会从当前文件指针的位置读取。
  • callback - 回调函数,有三个参数err, bytesRead, buffer,err 为错误信息, bytesRead 表示读取的字节数,buffer 为缓冲区对象。
js 复制代码
fs.open('./1.txt','r', (err, fd) => {
    if (err) {
        return console.error(err);
    }
    // UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。 现在'./1.txt'的内容为"a中小城镇下" 
    // 我想读取"小城镇"。
    // 那么length应该 3 * 3 = 9
    // 那么position的位置应该 a(1) + 中(3) = 4
    var buf = new Buffer.alloc(1024);
    let offset = 0
    let length = 9
    let position = 4
    fs.read(fd, buf, offset, length, position, (err, bytes, buf1) => {
        if (err){
           console.log(err);
        }
        console.log(bytes + "字节被读取");
        
        console.log('fs.read', buf.slice(offset, length).toString())
        console.log(buf === buf1)
        fs.close(fd, () => {
            console.log('fs.close')
        })
    });
})

2、目录相关操作

2.1、创建 fs.mkdir

fs.mkdir(path, options, callback)

  • path - 文件路径。
  • options 参数可以是:
    • recursive - 是否以递归的方式创建目录,默认为 false。
    • ode - 设置目录权限,默认为 0777。
  • callback - 回调函数,没有参数。
js 复制代码
fs.mkdir('./mkdir',(err) => {
    if(err) {
        return console.error(err);
    }
    console.log('fs.mkdir')
})

2.2、删除 fs.rmdir(path, callback)

  • path - 文件路径。
  • callback - 回调函数,没有参数。

只能删除空目录,如果该目录下有文件或者目录,都会删除失败。

js 复制代码
 fs.rmdir("./mkdir", (err) => {
    if (err) {
        return console.error(err);
    }
    console.log('fs.rmdir')
 })

2.3、读取 fs.readdir(path, callback)

  • path - 文件路径。
  • allback - 回调函数,回调函数带有两个参数err, files,err 为错误信息,files 为 目录下的文件数组列表。

它会读取这个目录下这一层级所有的文件(只有这一层的目录与文件,不包括子目录或者子文件)

js 复制代码
fs.readdir("./mkdir",function(err, files){
    if (err) {
        return console.error(err);
    }
    console.log('fs.readdir',files)
 });

3、文件权限说明

  • -rw------- (600) 只有拥有者有读写权限。
  • -rw-r--r-- (644) 只有拥有者有读写权限;而属组用户和其他用户只有读权限。
  • -rwx------ (700) 只有拥有者有读、写、执行权限。
  • -rwxr-xr-x (755) 拥有者有读、写、执行权限;而属组用户和其他用户只有读、执行权限。
  • -rwx--x--x (711) 拥有者有读、写、执行权限;而属组用户和其他用户只有执行权限。
  • -rw-rw-rw- (666) 所有用户都有文件读、写权限。
  • -rwxrwxrwx (777) 所有用户都有读、写、执行权限。

参考资料

path路径问题

node读取文件中,写入的文件路径分为两种,相对路径绝对路径,我们常用的就是相对路径如下:

js 复制代码
let url = './index.html' || index.html // 相对路径
let url = '/index.html' // 绝对路径 /斜杠开头就是绝对路径

/斜杠开头就是绝对路径

当node执行到此处的时候,会根据 终端位置来将这个相对路径进行解析转换成绝对路径,也就是说,这个相对是对于终端执行位置来说的。这个会导致的问题就是,当你的终端执行代码的时候位置不是符合你预期位置的时候,相对位置所读写的文件就会出现问题。

解决方案:全局变量__dirname与path模块

js 复制代码
__dirname 代表的是获取当前文件的绝对路径(不包括文件名)

const path = require('path')
path.resolve('__dirname', './index.html') // 这个方法可以将文件路径格式化特别是window系统与mac系统的路径问题。
path.resolve(绝对路径, 相对路径) 

额外补充一个知识点,网页里面的相对路径,它的相对是html所在的目录

js 复制代码
url: https://www.baidu.com/a/b/index.html 

index.html里面有
script src='../c/index.html'
那么就会被解析为
 https://www.baidu.com/a/c/index.html
相关推荐
清秋31 分钟前
全网最全 ECMAScript 攻略( 更新至 ES2026)
前端·javascript·编程语言
Cao_Ron1 小时前
用 SVG image symbol 画 ECharts graph 圆形节点
前端
hxy06011 小时前
React-Call让你的React组件像异步函数一样调用
前端·react.js
Hilaku1 小时前
为什么 AI 写前端时,总是优先选择 React,而不是 Vue?
前端·javascript·程序员
京东云开发者1 小时前
GPT-5.6、ChatGPT Work 与 Codex 更新食用指南
前端
Cao_Ron1 小时前
用 ECharts graph 画出卡片式矩形节点
前端
skiyee1 小时前
换个底座的 Wot Starter 居然让 AI 更懂项目!
前端·ai编程
anyup1 小时前
这一套绝了!AI 大模型写故事,星云 SDK 驱动 3D 数字人实时演绎
前端·人工智能·aigc
神奇小汤圆2 小时前
2026 Java岗面试题库(已收录GitHub),覆盖所有考点
后端
耀耀切克闹灬2 小时前
短链跳转
前端