学习资料
模块机制
CommonJs规范
Node与浏览器以及W3C组织、CommonJS组织、ECMAScript之间的关系,共同构成了一个繁荣的生态系统。
模块分类
一类是Node提供的模块,称为核心模块
;另一类是用户编写的模块,称为文件模块
。
部分核心模块是在node进程启动的时候就已经被加载进内存了。
文件模块则是在运行时动态加载.
模块导入
// 引入核心模块
require ('math')
// 引入文件模块
require ('./math.js')
注意:
当引入的模块是文件夹
的时候,node会在这个文件夹里面查看是否有package.json
,如果有则查看json里面的main属性所指引的文件,从而加载它,如果没有json文件或者main属性不存在,则去加载该文件夹下的index.js
或者index.json
模块导入的工作原理
- 将路径转为绝对路径
- 判断是否导入的对象是否已经存在于缓存中,如果存在则直接返回缓存,结束
- 根据路径获取模块里面的代码
- 用函数包裹改代码自动执行,如下
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)
- 缓存模块执行结果
这里需要注意的点就是:模块里面的代码只会执行一次
。
模块导出
module代表模块本身,exports是module的属性,exports的功能是将定义的函数或者变量导出。
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来调整内存限制的大小
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 类的简单使用
// 引入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字符串 以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'));
支持的转换编码
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 类(翻到下面)
写入与读取缓冲区
/**
* 需求
* 创建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文件
/**
* 需求,读取同文件下的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文件
/**
* 需求:写入一个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文件里面
/**
* 需求:
* 将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("程序执行完毕");
压缩一个文件
/**
* 需求:
* 压缩一个文件
*/
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("文件压缩完成。");
解压一个文件
/**
* 需求:
* 解压一个文件
*/
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)。
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 模式,所以如果文件存在,该方法写入的内容会覆盖旧的文件内容。
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个字节内容,其他的全部删除。
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')
})
});
})
fs.unlink 删整个文件内容
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
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 为缓冲区对象。
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 – 回调函数,没有参数。
fs.mkdir('./mkdir',(err) => {
if(err) {
return console.error(err);
}
console.log('fs.mkdir')
})
2.2、删除 fs.rmdir(path, callback)
- path – 文件路径。
- callback – 回调函数,没有参数。
只能删除空目录,如果该目录下有文件或者目录,都会删除失败。
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 为 目录下的文件数组列表。
它会读取这个目录下这一层级所有的文件(只有
这一层的目录与文件,不包括子目录或者子文件)
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读取文件中,写入的文件路径分为两种,相对路径
与绝对路径
,我们常用的就是相对路径如下:
let url = './index.html' || index.html // 相对路径
let url = '/index.html' // 绝对路径 /斜杠开头就是绝对路径
/斜杠开头就是绝对路径
当node执行到此处的时候,会根据 终端位置
来将这个相对路径进行解析转换成绝对路径,也就是说,这个相对
是对于终端
执行位置来说的。这个会导致的问题就是,当你的终端执行代码的时候位置不是符合你预期位置的时候,相对位置所读写的文件就会出现问题。
解决方案:全局变量__dirname
与path模块
__dirname 代表的是获取当前文件的绝对路径(不包括文件名)
const path = require('path')
path.resolve('__dirname', './index.html') // 这个方法可以将文件路径格式化特别是window系统与mac系统的路径问题。
path.resolve(绝对路径, 相对路径)
额外补充一个知识点,网页里面的相对路径,它的相对是html所在的目录
哦
url: https://www.baidu.com/a/b/index.html
index.html里面有
script src='../c/index.html'
那么就会被解析为
https://www.baidu.com/a/c/index.html
原文链接:https://juejin.cn/post/7336869936147939363 作者:phxxhp