path
是node中的内置模块,可以直接使用require将它导入,他的主要作用就是处理文件的目录和路径。只需要调用不同的方法。path相当于一个工具箱,只需要掌握它里面提供的工具,也就是方法。
const path = require('path');
basename(); 获取路径中基础名称
path.basename(__filename); // test.js
// 传入第二个参数如果匹配会省略后缀,不匹配仍旧返回真实的后缀
path.basename(__filename, '.js'); // test
path.basename('/a/b/c'); // c
path.basename('/a/b/c/'); // c
dirname(); 获取路径中的目录名称
path.dirname(__filename); // d:\Desktop\test
path.dirname('/a/b/c'); // /a/b
path.basename('/a/b/c/'); // /a/b
extname(); 获取路径中的扩展名称
path.extname(__filename); // .js
path.extname('/a/b'); //
path.extname('/a/b/index.html.js.css'); // .css
path.extname('/a/b/index.html.js.'); // .
isAbsolute(); 获取路径是否是绝对路径
path.isAbsolute('a'); // false
path.isAbsolute('/a'); // true
join(); 拼接多个路径片段,还原成完整可用路径
path.join('a/b', 'c', 'index.html'); // a/b/c/index.html
path.join('/a/b', 'c', 'index.html'); // /a/b/c/index.html
path.join('a/b', 'c', '../', 'index.html'); // a/b/index.html
resove(); 返回一个绝对路径
path.resove(); // 获取绝对路径
parse(); 解析路径
const obj = path.parse('/a/b/c/index.html');
/**
* root: /
* dir: /a/b/c
* base: index.html
* ext: .html
* name: index
*/
format(); 序列化路径,与parse功能相反, 将对象拼接成完整的路径。
path.format({
root: '/',
dir: '/a/b/c',
base: 'index.html',
ext: '.html',
name: 'index'
});
// /a/b/c/index.html
normalize(); 规范化路径,将不可用路径变为可用路径, 这个方法注意如果有转译字符会转译。
path.normalize('a/b/c/d'); // a/b/c/d
path.normalize('a//b/c../d'); // a/b/c../d
path.normalize('a\\/b/c\\/d'); // a/b/c/d
Buffer
Buffer一般称为缓冲区,可以认为因为Buffer的存在让开发者可以使用js操作二进制。IO行为操作的就是二进制数据。NodeJS中的Buffer是一片内存空间。他的大小是不占据v8内存大小的,buffer的内存申请不是由node生成的。只是回收的时候是v8的gc进行回收的。
buffer是nodejs中的一个全局变量,无需require就可以直接使用。一般配合stream流使用,充当数据的缓冲区。
alloc可以创建指定字节大小的buffer,默认没有数据
allocUnsafe 创建指定大小的buffer但是不安全,使用碎片的空间创建buffer,可能存在垃圾脏数据,不一定是空的。
from 接收数据创建buffer
在v6版本之前是可以通过实例化创建buffer对象的,但是这样创建的权限太大了,为了控制权限,就限制了实例化创建的方式。
// 创建buffer
const b1 = Buffer.alloc(10);
const b2 = Buffer.allocUnsafe(10);
from创建buffer可以接收三种类型,字符串,数组,buffer。 第二个参数是编码类型。
const b3 = Buffer.from('1');
const b4 = Buffer.from([1, 2, 3]);
Buffer的一些常见实例方法。
fill: 使用数据填充buffer,会重复写入到最后一位
write:向buffer中写入数据,有多少写多少,不会重复写入。
toString: 从buffer中提取数据
slice: 截取buffer
indexOf:在buffer中查找数据
copy: 拷贝buffer中的数据
Buffer的静态方法。
concat: 将多个buffer拼接成一个新的buffer
isBuffer: 判断当前数据是否是一个buffer
Buffer的split方法实现。
Array.Buffer.splice = function(sep) {
let len = Buffer.form(sep).length;
let ret = [];
let start = 0;
let offset = 0;
while(offset = this.indexOf(sep, start) !== -1) {
ret.push(this.slice(start, offset))
start = offset + len;
}
ret .push(this.slice(start));
return ret;
}
fs
在Node中Buffer和Stream随处可见,他们用于操作二进制数据。
FS是一个内置的核心模块,所有与文件相关的操作都是通过FS来进行实现的,比如文件以及目录的创建,删除,信息的查询或者文件的读取和写入。
如果想要操作文件系统中的二进制数据需要使用FS模块提供的API,这个过程中Buffer和Stream又是密不可分的。
介绍FS模块之前我们首先需要介绍一下文件系统的基础知识,比如权限位,标识符,文件描述符等。
权限是指当前的操作系统内不同的用户角色对于当前的文件可以执行的不同权限操作,文件的权限操作被分为r,w,x三种, r是读权限,w是写权限,x是执行权限。如果用8进制的数字进行表示r是4,w是2,x是1,如果不具备该权限就是一个0。
操作系统中将用户分为三类分别是文件的所有者,一般指的是当前用户自己,再有就是文件的所属组,类似当前用户的家人,最后是其他用户也就是访客用户。
Node中flag表示对文件操作方式,比如是否可读可写。
r: 表示可读
w: 表示可写
s: 表示同步
+: 表示执行相反操作
x: 表示排他操作
a: 表示追加操作
fd就是操作系统分配给被打开文件的标识,通过这个标识符文件操作就可以识别和被追踪到特定的文件。不同操作系统之间是有差异的,node为我们抹平了这种差异。
node每操作一个文件,文件描述符就会递增一次,并且它是从3开始的。因为0,1,3已经被输入,输出和错误占用了。后面我们在使用fs.open打开文件的时候就会得到这个fd。
fs任何文件操作api都有同步和异步两种方式,这里只演示异步API,同步基本也相同
- 文件读写
readFile: 从指定文件中读取数据
const fs = require('fs');
const path = require('path');
fs.readFile(path.resolve('aaa.txt'), 'utf-8', (err, data) => {
console.log(err);
console.log(data);
})
writeFile: 向指定文件中写入数据
fs.writeFile('bbb.txt', 'hello', {
mode: 438, // 操作位
flag: 'w+',
encoding: 'utf-8'
}, (err) => {
console.log(err);
})
appendFile: 追加的方式向指定文件中写入数据
fs.appendFile('bbb.txt', 'hello', {}, (err) => {
console.log(err);
})
copyFile: 将每个文件中的数据拷贝到另一个文件
fs.copyFile('aaa.txt', 'bbb.txt', (err) => {
console.log(err);
})
watchFile: 对指定文件进行监控
fs.watchFile('bbb.txt', {
interval: 20 // 20ms监控一次
}, (curr, prev) => {
console.log(curr); // 当前信息
console.log(prev); // 前一次信息
if (curr.mtime !== prev.mtime) {
// 文件被修改了
}
})
fs.unwatchFile('bbb.txt'); // 取消监控
- 文件打开与关闭
前面我们使用了fs实现了文件的读写操作,既然已经读写了就证明已经实现了文件的打开,为什么node还要单独的提供打开关闭的api呢?
因为readFile和writeFile的工作机制是将文件里的内容一次性的全部读取或者写入到内存里,而这种方式对于大体积的文件来讲显然是不合理的,因此需要一种可以实现边读编写或者边写边读的操作方式,这时就需要文件的打开、读取、写入、关闭看做是各自独立的环节,所以也就有了open和close。
const fs = require('fs');
const path = require('path');
// open
fs.open(path.resolve('aaa.txt'), 'r', (err, fd) => {
console.log(err);
console.log(fd);
fs.close(fd, (err) => {
});
})
- 目录操作
access: 判断文件或目录是否具有操作权限
fs.access('aaa.txt', (err) => {
console.log(err); // 存在错误就是没有权限
})
stat: 获取目录及文件信息
fs.stat('aaa.txt', (err, stat) => {
console.log(stat); // size isFile(), isDirectory()
})
mkdir: 创建目录
fs.mkdir('a/b/c', {
recursive: true, // 递归创建
}, (err) => {
console.log(err);
})
rmdir: 删除目录
fs.rmdir('a', {
recursive: true, // 递归删除
}, (err) => {
console.log(err);
})
readdir: 读取目录中内容, 不会递归子目录
fs.readdir('a', (err, files) => {
console.log(files);
})
unlink: 删除指定文件
fs.unlink('a', (err) => {
console.log(err);
})
- commonjs
CommonJS的出现是为了解决前端模块化,他的作者希望可以倒逼浏览器们实现前端模块化,但是由于浏览器本身具备的单线程阻塞的特点,CommonJS并不能适用于浏览器平台。CommonJS是一个超集,他是语言层面的规范,模块化只是这个规范中的一个部分。
- Events
Node中通过EventEmitter类实现事件统一管理。实际开发中基本很少引入这个类。这个类大部分供内置模块使用的, 比如fs、http都内置了这个模块。
Node是基于事件驱动的异步操作架构,内置events模块,模块提供了EventEmitter类,他的实例对象具备注册事件和发布事件删除事件的常规操作。
on:添加当事件被触发时调用的回调函数
emit: 触发事件,按照注册的顺序调用每个事件监听器
once: 注册执行一次的监听器
off:移除特定的监听器
const EventEmitter = require('events');
const ev = new EventEmitter();
ev.on('event', () => {
})
ev.emit('event');
- 事件环
在浏览器中是两个任务队列,一个是宏任务一个是微任务。但是在NodeJS中一共存在六个事件队列,timers,pending callbacks,idle prepare,poll,check,close callbacks。每一个队列里面存放的都是回调函数callback。
timer里面存在的是setTimeout与setInterval的回调函数
pending callback是执行操作系统的回调,例如tcp,udp。
idle,prepare只在系统内部进行使用。
poll执行与IO相关的回调操作
check中存放setImmediate中的回调。
close callbacks执行close事件的回调。
在node中代码从上到下同步执行,在执行过程中会将不同的任务添加到相应的队列中,比如说setTimeout就会放在timers中, 如果遇到文件读写就放在poll里面,等到整个同步代码执行完毕之后就会去执行满足条件的微任务。可以假想有一个队列用于存放微任务,这个队列和前面的六种没有任何关系。
当同步代码执行完成之后会去执行满足条件的微任务,一旦所有的微任务执行完毕就会按照上面列出的顺序去执行队列当中满足条件的宏任务。
首先会执行timers当中满足条件的宏任务,当他将timers中满足的任务执行完成之后就会去执行队列的切换,在切换之前会先去清空微任务列表中的微任务。
所以微任务执行是有两个时机的,第一个时机是所有的同步代码执行完毕,第二个时机队列切换前。
注意在微任务中nextTick的执行优先级要高于Promise,这个只能死记了。
setTimeout(() => {
console.log('s1');
})
Promise.resolve().then(() => {
console.log('p1');
})
console.log('start');
process.nextTick(() => {
console.log('tick');
})
setImmediate(() => {
console.log('st');
})
console.log('end');
// start end tick p1 s1 st
setTimeout(() => {
console.log('s1');
Promise.resolve().then(() => {
console.log('p1');
})
process.nextTick(() => {
console.log('t1');
})
})
Promise.resolve().then(() => {
console.log('p2')
})
console.log('start');
setTimeout(() => {
console.log('s2');
Promise.resolve().then(() => {
console.log('p3');
})
process.nextTick(() => {
console.log('t2');
})
})
console.log('end');
// start end p2 s1 s2 t1 t2 p1 p3
Node与浏览器事件环执行是有一些不同的。
首先任务队列数不同,浏览器一般只有宏任务和微任务两个队列,而Node中除了微任务队列外还有6个事件队列。
其次微任务执行时机不同,不过他们也有相同的地方就是在同步任务执行完毕之后都会去看一下微任务是否存在可执行的。对浏览器来说每当一个宏任务执行完成之后就会清空一次微任务队列。在node中只有在事件队列切换时才会去清空微任务队列。
最后在Node平台下微任务执行是有优先级的,nextTick优先于Promise.then, 而浏览器中则是先进先出。
setTimeout(() => {
console.log('timeout');
})
setImmediate(() => {
console.log('immdieate');
})
在node中时而会先输出timeout时而会先输出immdieate,这是因为setTimeout是需要接收一个时间参数的,如果没写就是一个0,我们都知道无论是在node还是在浏览器,程序是不可能真的是0,他会受很多的因素影响。这取决于运行的环境。
如果setTimeout先执行就会放在timers队列中,这样timeout就会先输入,如果setTimeout因为某些原因后执行了,那么check队列中的immdieate就会先执行。这就是为什么时而输出timeout时而输出immdieate。
const fs = require('fs');
fs.readFile('./a.txt', () => {
setTimeout(() => {
console.log('timeout');
}, 0)
setImmediate(() => {
console.log('immdieate');
})
})
这种情况就会一直先输出immdieate后输出timeout,这是因为,代码执行的时候会在timers里面加入timeout, 在poll 中加入fs的回调,在check中加入immdieate。fs的回调执行结束之后实在poll队列,队列切换的时候首先会去看微任务,但是这里没有微任务就会继续向下,下面就是check队列而不是timers队列,所以poll清空之后会切换到check 队列,执行immdieate回调。
- stream
流并不是Nodejs独创的内容,在linux系统中可以使用ls | grep *.js
命令操作,其实就是将ls命令获取到的内容交给grep去处理,这就是一个流操作。
使用流可以从空间和时间上提升效率,NodeJS诞生之初就是为了提高IO性能,其中最常用的文件系统和网络他们就是流操作的应用者。
NodeJS中流就是处理流式数据的抽象接口,NodeJS中的stream对象提供了用于操作流的对象。对于流来说只有多使用才能加深了解。
流的分段处理可以同时操作多个数据chunk,同一时间流无须占据大内存空间。流配合管道,扩展程序会变得很简单。
NodeJS中内置了stream模块,它实现了流操作对象。Stream模块实现了四个具体的抽象。所有的流都继承自EventEmitter。
Readable: 可读流,能够实现数据的获取。
Writeable: 可写流,能够实现数据的写操作。
Duplex: 双工流,即可度又可写。
Tranform: 转换流,可读可写,还能实现数据转换。
const fs = require('fs');
const rs = fs.createReadStream('./a.txt');
const ws = fs.createWriteStream('./b.txt');
rs.pipe(ws);
- 可读流
生产供消费的数据流。
const rs = fs.createReadStream('./a.txt');
const { Readable } = require('stream');
const source = ['a', 'b', 'c'];
class MyReadable extends Readable {
constructor() {
super();
this.source = source;
}
_read() {
const data = this.source.shift() || null;
this.push(data);
}
}
const myreadable = new MyReadable(source);
myreadable.on('data', (chunk) => {
console.log(chunk.toString());
})
- 可写流
用于消费数据的流,响应。
const ws = fs.createWriteStream('./b.txt');
const { Writable } = require('stream');
class MyWriteable extends Writable {
constructor() {
super();
}
_write (chunk, en, done) {
process.stdout.write(chunk.toString());
process.nextTick(done);
}
}
const mywriteable = new MyWriteable();
mywriteable.write('yindong', 'utf-8', () => {
consoel.log('end');
})
- Duplex
const { Duplex } = require('stream');
class MyDuplex extends Duplex {
constructor(source) {
super();
this.source = source;
}
_read() {
const data = this.source.shift() || null;
this.push(data);
}
_write(chunk, en, next) {
process.stdout.write(chunk);
process.nextTick(next);
}
}
const source = ['a', 'b', 'c'];
const myDuplex = new MyDuplex(source);
mtDuplex.on('data', (chunk) => {
console.log(chunk.toString());
})
mtDuplex.write('yindong', () => {
console.log('end');
})
- Transform
const { Transform } = require('stream');
class MyTransform extends Transform {
constructor() {
super();
}
_transform(chunk, en, cb) {
this.push(chunk.toString().toUpperCase());
cb(null);
}
}
const t = new MyTransform();
t.write('a');
t.on('data', (chunk) => {
console.log(chunk.toString());
})
- 链表
链表是一种数据存储结构。
在文件可写流的write方法工作的时候,有些被写入的内容需要在缓冲区排队等待的,而且遵循的是先进先出的规则,为了保存这些排队的数据,在新版的Node中就采用了链表的结构来存储这些数据。
相比较数组来说,链表的优势更明显,在多个语言下,数组存放数据的长度是有上限的,数组在执行插入或者删除操作的时候会移动其他元素的位置,并且在JS中数组被实现成了一个对象。所以在使用效率上会低一些。
当然这都是相对的,实际应用中数组的作用还是很强大的。
链表是由一系列节点组成的集合。这里的节点都称为node节点,每个节点的身上都有一个对象的引用是指向下一个节点,将这些指向下一个节点的引用组合到一起也就形成了一个链,对于链表结构来说我们常听到的会有不同类型,双向链表,单向链表,循环链表。常用的一般是双向链表。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!