NodeJS 基础 API
在介绍 NodeJS 的基础 API 前,先抛出 API 中文文档地址:http://nodejs.cn/api/
path
path 顾名思义就是与路径相关的一切,在 path 模块中提供了一些工具函数,用于处理文件与目录的路径。我们可以通过以下方式使用:
const path = require('path');
normalize – 该方法将我们传入的可能不太规范的路径转化成规范的路径
这么说可能还是太模糊,我们看几个例子就明白了
// 使用 ES6 的语法,相当于 const normalize = require('path').normalize</span>
const {normalize} = require('path');
console.log(normalize('/user//local/bin'));
console.log(normalize('/user/local/../bin'));
输出的结果为:
显而易见,第一个将我们输入的不规范的 “//” 转化成了 “/”,第二个我们输入的 “…/” 代表的是上一级目录,在输出时给我们修改成了 “/user/bin”,删除了 “/local”
这就是 normalize 的作用,将我门输入的不规范的路径规范化
join – 顾名思义就是拼接的意思,它会将传入的路径进行拼接,其内部会调用 normalize 方法去处理不规范的路径
const {join} = require('path');
console.log(join('/user/','/local/','/bin'));
console.log(join('/user','../local/..','/bin'));
输出结果为:
resolve – 将相对路径转化为绝对路径
const {resolve } = require('path');
console.log(resolve ('./'));
其输出结果为:
输出的就是你当前项目所在的绝对目录
几个与名字相关 API
/*
* basename -- 文件名
* dirname -- 文件所在的目录
* extname -- 文件的扩展名
*/
const {basename ,dirname ,extname} = require('path');
const path = "/user/local/bin/no.txt";
console.log(basename(path));
console.log(dirname(path));
console.log(extname(path));
输出的结果为:
parse – 返回一个包含 path 所有元素的对象
const {parse} = require('path');
const path = "/user/local/bin/no.txt";
console.log(parse(path));
其输出的结果为:
format – 从一个对象返回一个字符串,与 path.parse() 相反
先来看一个例子
const {parse} = require('path');
const path = "/user/local/bin/no.txt";
const parsePath = parse(path);
// 将 parse 处理后的对象保存起来,通过 format 进行处理
console.log(format(parsePath));
其输出结果为:
除此之外,format 还有几个值得注意的特性:
当 format(pathObject) 中传入的属性有组合时,有些属性的优先级比其它的高:
- 如果提供了 pathObject.dir ,则 pathObject.root 将会被忽略
- 如果提供了 pathObject.base,则 pathObject.ext 和 pathObject.name 会被忽略
注:一般情况下,很少使用这个方法,但是当我们需要更改目录或文件时,用这种方法就相对简单
sep – 输出特定平台的路径片段的分隔符
const {sep } = require('path');
console.log(sep);
其输出结果为:
这个是在 windows 系统下的路径片段的分隔符
delimiter – 输出特定平台路径分隔符
const {delimiter } = require('path');
console.log(delimiter );
其输出结果为:
这个是在 windows 系统下的路径分隔符
win32 与 posix – 这两个属性使得 path 可以跨平台查看
win32 :可以强制当前系统使用 windows 的系统,使得 NodeJS 的 path 模块也遵循 windows 的风格
posix :可以强制当前系统使用 posix 的系统,使得 NodeJS 的 path 模块也遵循 posix 的风格
此时,我们就可以通过这两个命令来再次执行 exp 与 delimiter,来查看在 posix 下的路径段分隔符以及路径分隔符
我们上面看过 windows 环境下的 exp 与 delimiter,所以现在我们来看一下 posix 下的
const {posix,sep,delimiter} = require('path');
console.log(posix.sep);
console.log(posix.delimiter);
其输出结果为:
小结:
现在我们知道和 path 相关的包括 “__dirname”,"__filename",“process.cwd()”,那么它们之间又有什么区别呢?
“__dirname”,"__filename" 总是返回文件的绝对路径,而 “process.cwd()” 则是返回执行 node 命令所在的文件夹的路径。
除了它们之外,"./" 以及 “…/” 在不同的情况下也会有不同的意思,在 require 方法中总是相对当前文件所在的文件夹,但是在其它地方则是和 “process.cwd()” 一样,相对于执行 node 命令所在的文件夹
Buffer
在介绍 Buffer API 之前,我们先来看几个 Buffer 的特点
- Buffer 用于处理二进制数据流
- 实例类似整数数组,大小固定
- 运用 C++ 代码在 V8 堆外分配物理内存
- 用于 Buffer 使用频率非常高,所以 NodeJS 将 Buffer 挂到了 global 上,我们可以直接使用
实例化 Buffer 的方法
(1)Buffer.alloc(size[,fill[,encoding]])
- size – 新建的 Buffer 期望的长度(创建后长度无法改变
- fill – 用来填充新建的 Buffer 的值,默认:0
- encoding – 该值是它的字符编码,默认值:‘utf8’
// 创建一个长度为 10,且用 0 填充的 Buffer
const buf1 = Buffer.alloc(10);
console.log(buf1);
// 创建一个长度为 10,且用 0×1 填充的 Buffer (0× 表示 16 进制)
const buf2 = Buffer.alloc(10,1);
console.log(buf2);
// 创建一个长度为 10,用 'aGVsbG8gd29ybGQ=' 填充,以 base64 来编码的 Buffer
const buf3 = Buffer.alloc(10,'aGVsbG8gd29ybGQ=','base64');
其输出结果为:
(2)Buffer.allocUnsafe(size)
- size – 新建的 Buffer 期望的长度
- 以此方法创建的 Buffer 实例的底层是未初始化的,所以新建的 Buffer 的内容是未知的,因此有可能暴露敏感数据,所以使用 allocUnsafe() 时需要使用 fill() 或 write() 重写(尽量不要使用 allocUnsafe())
// 创建一个长度为 10,未初始化的 Buffer
const buf4 = Buffer.allocUnsafe(10);
console.log(buf4);
其输出结果为:
可以看出新建的 Buffer 是未初始化的,里面的内容可能暴露敏感数据
(3)Buffer.from()
- 根据给定的参数进行实例化
// 创建一个包含 [0×1,0×2,0×3] 的 Buffer
const buf5 = Buffer.from([1,2,3]);
console.log(buf5);
// 创建一个包含 UTF-8 字节的 Buffer
const buf6 = Buffer.from('test');
console.log(buf6);
// 创建一个包含 base64 字节的 Buffer
const buf7 = Buffer.from('test','base64');
console.log(buf7);
其输出结果为:
Buffer 类所带的一些常用方法
(1)Buffer.byteLength(string[,encoding])
- 返回一个字符创的实际字节长度(与 .length 不同,.length 返回的是字符串的字符数)
- string – 要计算长度的值
- encoding – 字符编码,默认:‘utf8’
// 返回 test 的字节长度
const buf8 = Buffer.byteLength('test');
console.log(buf8);
// 返回 你好 的字节长度
const buf9 = Buffer.byteLength('你好');
console.log(buf9);
其输出结果为:
可以看出,英文每个字母占一个字节,而中文一个文字占三个字节
(2)Buffer.isBuffer(obj)
- 看名字就应该知道了,这个方法使用来判断一个对象时不是一个 Buffer
// 传入一个普通的对象
const buf10 = Buffer.isBuffer({});
console.log(buf10);
// 传入一个 Buffer
const buf11 = Buffer.isBuffer(Buffer.from([1,2,3]));
console.log(buf11);
其输出结果为:
(3)Buffer.concat(list[,totalLength])
- list – 要合并的 Buffer 实例的数组
- totalLength – 限制合并 list 后得到的 Buffer 的长度
const buf1 = Buffer.from('This ');
const buf2 = Buffer.from('is ');
const buf3 = Buffer.from('a ');
const buf4 = Buffer.from('test ');
const buf5 = Buffer.from('!');
// 合并 Buffer
const bufA = Buffer.concat([buf1 , buf2 , buf3 ,buf4 ,buf5]);
console.log(bufA.toString());
其输出结果为:
我们增加上第二个参数看一看效果:
const buf1 = Buffer.from('This ');
const buf2 = Buffer.from('is ');
const buf3 = Buffer.from('a ');
const buf4 = Buffer.from('test ');
const buf5 = Buffer.from('!');
// 合并 Buffer
const bufA = Buffer.concat([buf1 , buf2 , buf3 ,buf4 ,buf5],6);
console.log(bufA.toString());
其输出结果为:
可以看到,加上 totalLength 时,如果合并得到的 Buffer 的长度超过了 totalLength,则合并的结果将会被截断为 totalLength 的长度。
Buffer 实例的一些常用方法
buf.length
- 返回 Buffer 所占的长度
const buf1 = Buffer.from('This is a test !')
console.log(buf1.length);
输出的结果为:
注:buf.length 返回的是 Buffer 所占的空间,与其内部可用的数据量无关
buf.toString([encoding[,start[,end]]])
- encoding – 解码使用的字符编码,默认:utf8
- start – 从第几个开始解码,默认:0
- end – 到第几个结束解码,默认:buf.length
const buf1 = Buffer.from('This is a test !')
// 使用 base64 进行解码
console.log(buf1.toString('base64'));
// 从第一个开始解码到第五个结束解码
console.log(buf1.toString('base64',1,5));
其输出结果为:
buf.fill(value[,offset[,end]][,encoding])
- value – 用来填充 buf 的值
- offset – 开始填充 buf 的位置,默认:0
- end – 结束填充 buf 的位置(不包含),默认:buf.length
- encoding – 使用的字符编码,默认:utf8
const buf = Buffer.alloc(10);
console.log(buf);
// 使用 buf.fill() 填充 buf
console.log(buf.fill(10,2,6));
其输出结果为:
buf.equals(otherBuffer)
- otherBuffer – 要进行比较的 Buffer
- 如果两个 Buffer 具有完全相同的字节,则返回 true,否则返回 false
const buf1 = Buffer.from('abc');
const buf2 = Buffer.from('abcd');
const buf3 = Buffer.from('abc');
// 使用 buf1 与 buf2 以及 buf3 是否相等
console.log(buf1.equals(buf2));
console.log(buf1.equals(buf3));
其输出的结果为:
buf.indexOf(value[,byteOffset][,encoding])
- value – 要搜索的值
- byteOffset – 开始搜索的位置,默认:0
- encoding – 使用的字符编码,默认:utf8
const buf1 = Buffer.from('This is a test !')
console.log(buf1.indexOf('This'));
console.log(buf1.indexOf('this'));
其输出结果为:
buf.copy(target[,targetStart[,sourceStart[,sourceEnd]]])
- target – 要拷贝进的 Buffer
- targetStart – 从 target 中的 Buffer 的什么位置开始拷贝
- sourceStart – 从 buf 中的 Buffer 的什么位置开始拷贝
- sourceEnd – 从 buf 中的 Buffer 的什么位置结束拷贝
const buf1 = Buffer.allocUnsafe(10);
const buf2 = Buffer.alloc(10,1);
console.log(buf2);
buf1.copy(buf2,0,0,5);
console.log(buf2.toString());
其输出结果为:
中文乱码问题的解决方法
出现中文乱码的根本原因在于一个中文字符需要使用三个字节来表示,拆分有误或者其它情况下就会造成无法解码,从而出现乱码
先举个例子看看中文乱码的出现原因
const buf = Buffer.from('中文字符串!')
for (let i = 0; i < buf.length; i += 5){
const buf2 = Buffer.allocUnsafe(5);
buf.copy(buf2,0,i);
console.log(buf2.toString());
}
此时的结果为:
现在我们需要借助一个内置的 StringDecoder 来进行优化
const StringDecoder = require('string_decoder').StringDecoder;
// 实例化 StringDecoder
const decoder = new StringDecoder('utf8');
const buf = Buffer.from('中文字符串!')
for (let i = 0; i < buf.length; i += 5){
const buf2 = Buffer.allocUnsafe(5);
buf.copy(buf2,0,i);
console.log(decoder.write(buf2));
}
现在的输出结果就没有乱码了
其实 StringDecoder 当设置上编码类型时,它就会根据对应的编码类型进行处理,因为一个中文占三个字节,而本案例以五个字节为基准做输出,自然有一部就无法识别,就会出现乱码现象,这时我们使用 StringDecoder ,并将其编码格式设置为 ‘utf8’,它就会按照三个字节一个文字的方法进行编译,一旦遇到不符合的,它不会进行编译而是保留下来,与下一个进行组合直到满足编译条件为止
Events
一开始我们就说过 NodeJS 最重要的两大特点就是非阻塞 I/O 以及事件驱动
而执行事件驱动就是依靠的 events 这个模块
大多数的 NodeJS 的核心 API 都是采用的异步事件驱动,而某些类型的对象(触发器)会周期性地触发命名事件来调用函数对象(监听器)
我们先来看一个简单的例子,了解一下怎么使用 events
// 引用 events 模块
const EventEmitter = require('events');
// 定义一个 class 类,使用 ES6 的语法
class MyEmitter extends EventEmitter{};
const myEmitter = new MyEmitter;
// 绑定相应的事件
myEmitter.on('event',()=>{
console.log('触发了一个事件')
});
// 手动触发事件
setInterval(() => {
myEmitter.emit('event')
},500);
其输出结果为:
可以看出每隔 500 毫秒就会调用一遍绑定的 event 事件
再来看一个关于错误提示的通用小 dom
// 引用 events 模块
const EventEmitter = require('events');
// 定义一个 class 类,使用 ES6 的语法
class MyEmitter extends EventEmitter{};
const myEmitter = new MyEmitter;
// 绑定相应的事件,传入 err 参数获取错误信息
myEmitter.on('error',(err)=>{
console.log(err)
});
// 手动调用 error 传入相关错误信息
myEmitter.emit('error' , New Error('this is error'));
其输出结果为:
可以看出我们已经将错误信息等内容都打印了出来
如果我们想传递更多的参数可以这样写
myEmitter.emit('error' , New Error('this is error'),Date.now());
如果我们只想让事件响应程序只执行一次我们可以使用 once 进行绑定
// 引用 events 模块
const EventEmitter = require('events');
// 定义一个 class 类,使用 ES6 的语法
class MyEmitter extends EventEmitter{};
const myEmitter = new MyEmitter;
myEmitter.once('test',()=>{
console.log('test')
});
setInterval(() =>{
myEmitter.emit('test');
},500)
其输出结果为:
虽然我们使用了 setInterval 但是仍然只调用了一次
有时候我们需要移除某个事件上的处理程序,我们可以使用 removeListener 或 removeAllListeners,来看一个小 dom
// 引用 events 模块
const EventEmitter = require('events');
// 定义一个 class 类,使用 ES6 的语法
class MyEmitter extends EventEmitter{};
function fn1() {
console.log('fn1');
};
function fn2() {
console.log('fn2');
};
const myEmitter = new MyEmitter;
myEmitter.on('test',fn1);
myEmitter.on('test',fn2);
setInterval(() =>{
myEmitter.emit('test');
},500);
// 1500 毫秒后移除 fn1
setTimeout(() => {
// removeListener 接受两个参数,一个是要从何监听器中移除,另一个是移除这个监听器中的哪个事件
myEmitter.removeListener('test',fn2)
},1500);
其输出结果为:
可以看出在 1500 毫秒后 fn2 被移除了,只输出 fn1
removeAllListeners 的用法同 removeListener 一样只是只用传入一个参数,即要移除的监听器
fs
fs 是 File System 文件系统的全称,fs 模块提供了一些 API ,用于以一种类似标准 POSIX 函数的方式与文件系统进行交互
在使用之前,我们需要先将其引入:
const fs = require('fs');
所有的文件系统的操作都有异步和同步两种形式
异步形式的最后一个参数都是完成时的回调函数,传给回调函数的参数取决于具体的方法,但回调函数的第一个参数都会保留给异常;如果操作成功完成,则第一个参数会是 null 或 undefined
而在使用同步操作时,任何异常都会被立即抛出,可以使用 try/catch 来处理异常
但是由于高并发的存在,使用同步有可能会导致有些用户无法访问,因此大部分情况下我们都使用异步的方法
首先来看一下一些常用的 API
readFile(path[,options],callback) – 读取文件(异步)
- path – 要读取文件的路径
- options – 指定字符编码
- callback – 回调函数
// 引用 fs 模块
const fs = require('fs');
// 读取文件
fs.readFile('./join.js','utf8',(err,data) => {
// 如果有错误则抛出错误
if (err) throw err;
console.log(data)
});
在没有错误的情况下,其输出结果为:
此时就读出了 “./join.js” 中的内容
如果出现错误,则会抛出错误:
readFileSync(path[,options]) – 读取文件(同步)
- path – 要读取文件的路径
- options – 指定字符编码
// 引用 fs 模块
const fs = require('fs');
// 同步方法使用 try/catch 捕获错误
try{
// 如果没错则打印出数据
const read = fs.readFile('./join.js','utf8');
console.log(read);
}catch(err){
// 如果有错则抛出错误
console.log(err);
};
正确的输出:
错误的输出:
writeFile(file,data[,options],callback) – 写文件(异步)
- file – 文件名
- data – 要写入的内容
- options – 指定的字符编码(如果 data 是一个 buffer 则 options 指定的字符编码无效)
- callback – 回调函数
// 引用 fs 模块
const fs = require('fs');
// 写文件
fs.writeFile('test','这是一个测试','utf8',(err) => {
if(err) throw err;
console.log("文件以保存");
});
其输出的结果如图:
上面说了 data 参数除了传字符串还可以传入 buffer,我们再来看一下传入 buffer 的例子
// 引用 fs 模块
const fs = require('fs');
// 创建一个 buffer
const content = Buffer.from('这是一个测试');
// 写文件
fs.writeFile('test',content,(err) => {
if(err) throw err;
console.log("文件以保存");
});
其结果与之前是一样的,只不过 data 参数传入 buffer 后,后面的 options 参数所指定的字符集将失效
stats(path,callback) – 获取文件信息
- path – 要获取文件信息的文件路径
- callback – 回调函数
// 引用 fs 模块
const fs = require('fs');
fs.stat('./stat.js',(err,stats) => {
if(err){
console.log('文件不存在');
return
};
// 判断是不是一个文件
console.log(stats.isFile());
// 判断是不是一个文件夹
console.log(stats.isDirectory());
console.log(stats);
});
其输出的结果为:
rename(oldPath,newPath,callback) – 重命名
- oldPath – 想要重命名的文件的路径
- newPath – 重命名的名字
- callback – 回调函数
// 引用 fs 模块
const fs = require('fs');
fs.rename('./test','text.txt',(err) => {
if(err) throw err
console.log('文件已重命名');
});
重命名前的文件:
重命名后的文件:
unlink(path,callback) – 删除文件
- path – 要删除的文件的路径
- callback – 回调函数
// 引用 fs 模块
const fs = require('fs');
fs.unlink('./text.txt',(err) => {
if(err) throw err
console.log('文件已删除');
});
删除前:
删除后:
readdir(path[,options],callback) – 读取文件夹
- path – 要读取的文件夹的路径
- options – 指定的字符编码,默认:utf8
- callback – 回调函数
// 引用 fs 模块
const fs = require('fs');
fs.readdir('./',(err,files) => {
if(err) throw err
console.log(files);
});
就可以把当前目录下的文件夹都输出出来:
mkdir(path,callback) – 创建文件夹
- path – 要创建文件夹的路径
- callback – 回调函数
// 引用 fs 模块
const fs = require('fs');
fs.mkdir('./test',(err) => {
if(err) throw err
console.log('文件夹已创建');
});
执行之后就会生成一个名叫 test 的文件夹:
rmdir(path,callback) – 删除文件夹
- path – 要删除的文件夹路径
- callback – 回调函数
// 引用 fs 模块
const fs = require('fs');
fs.rmdir('./test',(err) => {
if(err) throw err
console.log('文件夹已删除');
});
执行后名为 test 的文件夹就会被删除:
watch(filename[,options][,listener]) – 监视文件的变化
- filename – 要监视的文件或目录
- options – 如果 options 是一个字符串,则是指定了字符编码,否则应该传入一个对象
- persistent – 指明如果文件正在被监视,进程是否继续,默认:true
- recursive – 指明是否监视全部子目录,默认:false
- listener – 监视器回调
- eventType – 指触发事件的类型
- filename – 指触发事件的文件的名称
// 引用 fs 模块
const fs = require('fs');
fs.watch('./',{recursive:true},(eventType,filename) => {
console.log(eventType,filename);
});
执行文件,出现如图所示时,则表明已经开启了监视:
现在我们来修改一下这个 watch.js 文件,再看一下有什么变化:
我们增加一个文件看一看:
此时,再将刚才创建的 html 文件删除,看一下会打印出什么:
那么,问题来了,‘change’、‘rename’ 是什么意思呢?
- change – 当一个文件改变时会出现
- rename – 当一个文件或目录出现或消失时会出现
fs 流
上面的方法都是一次性完整的读取或写入文件,这对小文件的操作没有问题,但是当操作较大的文件时,由于内存的限制,就会出现问题。因此,我们引入了一种 fs 流的方式来读取文件
流 – 就是一种有方向的数据,从一个设备(文件)流向另一个设备(文件)
fs 流与 fs 直接读取的区别 – fs 直接读取是将数据一次性的完整的读取出来放入缓存中,而 fs 流的方式则是将数据转换为流,之后再一点点的传到另一个设备(文件)中
流读取
// 引用 fs 模块
const fs = require('fs');
const rs = fs.createReadStream('./readStream');
// 使用 pipe 管道将其写入控制台
rs.pipe(process.stdout);
注:管道 pipe:提供了一个输出流到输入流的机制,当一个对象的流入速度与流出速度不
一致时,有可能会出现阻塞,这就是 pipe 管道机制的用处
其输出的结果为:
流写入
// 引用 fs 模块
const fs = require('fs');
const rw = fs.createWriteStream('./text.txt');
// 模拟网络慢的情况
const tid = setInterval(() => {
const num = parseInt(Math.random() * 10);
if(num < 8){
// 写入
rw.write(num+'');
}else{
// 清除定时器
clearInterval(tid);
// 结束写入
rw.end();
}
},200);
// 如果执行了 rw.end() 方法则会触发这个事件
rw.on('finish',() => {
console.log('done')
});
执行后当期文件夹下多了一个 text.txt 文件:
要注意的是我们进行写入操作时即 ‘rw.write(num + ‘’)’ ,写入的数据必须是字符串或者 buffer 格式
promisify – 解决回调地狱的问题
使用 promise 的方法解决回调地狱
// 引用 fs 模块
const fs = require('fs');
// 引入 promisify 模块(node 8 以上版本)
const promisify = require('util').promisify;
// 使用 promisify 模块调用 fs.readFile
const read = promisify(fs.readFile);
// 使用 promise 的方法来读取文件
read('./promisify.js').then((data) => {
console.log(data.toString());
}).catch((err) => {
console.log(err);
});
其输出的结果为:
使用 async、await 方法解决回调地狱
// 引用 fs 模块
const fs = require('fs');
// 引入 promisify 模块(node 8 以上版本)
const promisify = require('util').promisify;
// 使用 promisify 模块调用 fs.readFile
const read = promisify(fs.readFile);
// 使用 async、await 的方法来读取文件
async function test(){
try{
const content = await read('./promisify.js');
console.log(content.toString());
}catch(err){
console.log(err)
};
};
test();
其结果为: