~ Node.js 内置模块:fs / path
与浏览器程序相比,Node.js 作为服务器端程序,其最大的特点在于:没有了浏览器的安全限制。
相对应的,它必须能够接受网络需求、读写文件操作和处理二进制内容。
Node.js 常用模块,实现了服务器端程序的一些基本功能,其中:
- fs 模块:文件及目录操作,即,对系统文件和目录进行读写操作
- path 模块:路径处理,包含一系列处理和转换文件路径的工具集
编程风格( 异步和底层操作 ):所有方法都有异步( 基于回调函数的编程风格,推荐 )和同步的形式
异步 I/O input/output 1. 文件操作 2. 网络操作 在浏览器中也存在异步操作: 1. 定时任务 2. 事件处理 3. Ajax 回调处理 JS的运行时是单线程的,但引入了事件队列机制。所以,Nodejs中的事件模型与浏览器中的事件模型类似: 单线程 + 事件对列机制 Nodejs中异步执行的任务:文件I/O、网络I/O
fs 模块
// 引入模块
const fs = require('fs');
[1] 文件操作
- 获取文件信息
fs.stat(path,callback)其中,path是文件路径;callback是回调函数,带有两个参数:err,stats// 代码示例 const fs = require('fs'); fs.stat('./a.json',(err,stats)=>{ if(err) return; console.log(stats); })stats 是 fs.Stats 类的对象,其属性如下:
/* Stats { ... atime: 2020-12-01T03:51:50.561Z, // 文件访问的时间 mtime: 2020-12-01T03:29:35.765Z, // 文件数据发生变化的时间 ctime: 2020-12-01T03:29:35.765Z, // 文件的状态(如权限)发生变化的时间 birthtime: 2020-12-01T03:26:42.874Z // 文件创建的时间 } */其方法包括( 可以用这些方法判断文件的类型 ):
- stats.isFile() 如果是文件,返回 true,否则返回 false。
- stats.isDirectory() 如果是目录,返回 true,否则返回 false。
...
// 代码示例 let fs = require('fs'); fs.stat('./a.json',(err,stats)=>{ if(stats.isFile()){ console.log('文件类型'); }else if(stats.isDirectory()){ console.log('目录类型') } })
- 读取文件
fs.readFile(file[,options],callback)其中,file是文件名;callback,是一个回调函数,有两个参数:err 和 data(文件内容);而,第二个参数是编码 — 不写,默认为 null,则 data 是一个 buffer 实例对象;如果写了,如 utf8,则回调函数获取到的数据就是字符串。# 代码示例 fs.readFile('./a.json',(err,data)=>{ console.log(data); // 数据是一个buffer对象 }) fs.readFile('./a.json','utf8',(err,data)=>{ console.log(data); // 获取到的数据是字符串 })
- 写入文件 // 如果文件不存在,则新建;如果存在,则会被替换
- 追加文件 // 追加数据到一个文件中;如果不存在,则创建文件
fs.appendFile(file,data[,option],callback)其中,file是文件名;data是要追加写入文件的数据,可以是字符串,也可以是buffer对象;options,编码,不写则默认为utf8;callback,回调函数,只有一个参数:err# 代码示例 fs.appendFile('./a.js','world',(err)=>{ if(err){ console.log(err) } })
- 删除文件
fs.unlink(path,callback)其中,path为文件路径;callback,回调函数,只有一个参数:err# 代码示例 fs.unlink('./abc/a.js',(err)=>{ if(err){ console.log(err); } console.log('删除文件成功'); })
- 重命名文件( 类似于:剪切,然后,重命名 )
fs.rename(oldpath,newpath,callback)其中,oldpath是被重命名的文件路径;newpath是重命名后的新文件路径;callback,回调函数,只有一个参数:err# 代码示例:会删除a.js文件,在新路径上创建b.js文件 fs.rename('./abc/a.js','./b.js',(err)=>{ if(err){ console.log(err); } })
- 文件的流式操作( 主要用于大文件处理 )
/* fs.createReadStream(path[,option]) 读文件 fs.createWriteStream(path[,option]) 写文件 readstream.pipe(writestream) 直接把输入流和输出流关联起来 */ const path = require('path'); const fs = require('fs'); let spath = path.join(__dirname,'file.zip'); // 原路径 let dpath = path.join('/Users/admin/webpiece','file.zip'); // 目标路径 let readstream = fs.createReadStream(spath); // 读文件的数据流 let writestream = fs.createWriteStream(dpath); // 写文件的数据流 // 基于事件的处理方式 // readstream.on('data',(chunk)=>{ // data,是一个事件,当读取数据的时候触发,每读取一次,触发一次;chunk参数的意思是“一块数据” // writestream.write(chunk); // }); // readstream.on('end',()=>{ // 数据处理完成的时候触发“end”事件 // console.log('文件处理完成'); // }); // 上述注释的内容,可以直接写为: // pipe 的作用是直接把输入流和输出流关联起来 readstream.pipe(writestream);所以,大文件的流式操作可以简写为:
const path = require('path'); const fs = require('fs'); let spath = path.join(__dirname,'file.zip'); let dpath = path.join('/Users/admin/webpiece','file.zip'); fs.createReadStream(spath).pipe(fs.createWriteStream(dpath))
[2] 目录操作
- 创建目录
fs.mkdir(path,callback)其中,path为目录的路径;callback,回调函数,只有一个参数:err// 在当前目录创建 abc 目录 fs.mkdir('abc',(err)=>{ if(err){ console.log(err); } }) 或 // 在当前目录下创建 abcd 目录 let path = require('path'); fs.mkdir(path.join(__dirname,'abcd'),(err)=>{ if(err){ console.log(err); } })
- 删除目录
fs.rmdir(path,callback)其中,path为目录的路径;callback,回调函数,只有一个参数:err# 代码示例 fs.rmdir('abcd',(err)=>{ if(err){ console.log(err) } })
- 读取目录
fs.readdir(path[,option],callback)其中,path为文件路径;option,编码,不写,则默认为utf8;callback,回调函数,有两个参数:err 和 files,而,files 表示 path 中不包括 . 和 .. 在的目录/文件的数组# 代码示例 fs.readdir(__dirname,(err,files)=>{ if(err)return; console.log(files); // files表示当前目录下的所有目录和文件 // [ 'a.json', 'abc', 'app.js', 'b.js', 'package.json' ] })
[3] 代码示例:对 files 进行遍历,并通过 fs.stat() 对文件类型进行判断
let fs = require('fs');
let path = require('path');
fs.readdir(__dirname,(err,files)=>{
files.forEach((item,index)=>{ // foreach遍历
fs.stat(path.join(__dirname,item),(err,stats)=>{
if(stats.isFile()){
console.log(item,'文件');
}else if(stats.isDirectory()){
console.log(item,'目录');
}
})
})
})
path 模块
// 引入模块
let path = require('path');
[1] 路径的基本结构 // dirname + basename(extname)
- 获取文件的全路径
console.log(__filename); // /Users/admin/webpiece/Project/test/test/app.js
- dirname
console.log(path.dirname(__filename)); // /Users/admin/webpiece/Project/test/test console.log(__dirname); // /Users/admin/webpiece/Project/test/test
- basename
console.log(path.basename(__filename)); // app.js console.log(path.basename(__filename,'.js')); // app // 不含扩展名
- extname
console.log(path.extname(__filename)); // .js
[2] 路径的格式化处理
- path.parse() 将一个字符串路径转换为对象格式(string->obj)
console.log(path.parse(__filename)); // 结果如下: let obj={ root: '/', dir: '/Users/admin/webpiece/Project/test/test', base: 'app.js', ext: '.js', name: 'app' }
- path.format() 将一个对象格式化为字符串路径(obj->string)
console.log(path.format(obj)); // /Users/admin/webpiece/Project/test/test/app.js
[3] 路径分隔符
- path.sep 返回对应平台下的文件分隔符,win下为'\',*nix下为'/'
console.log('far/bar/baz'.split(path.sep)); // win下返回['foo/bar/baz'],但在*nix系统下会返回['foo','bar','baz']
- path.delimiter 返回对应平台下的路径分隔符,win下为';',*nix下为':'
[4] 路径规范化 & 路径拼接
- path.normalize(p) 规范化路径,处理冗余的“..”、“.”、“/”字符
- 发现多个斜杠时,会替换成一个斜杠
- 当路径末尾包含一个斜杠时,保留。Windows系统使用反斜杠
简单来说就是,把一个不太合理的路径进行格式化,形成一个规范的路径
console.log(path.normalize('a/b/c/../user/bin'));//a\b\user\bin console.log(path.normalize('a/b/c///../user/bin/'));//a\b\user\bin\
- path.join([path1], [path2], [...]) 路径拼接 — 将多个路径结合在一起,并转换为规范化路径
console.log(path.join('////./a', 'b////c', 'user/'));//\a\b\c\user console.log(path.join(__dirname,'a.json')); // /Users/admin/webpiece/Project/test/test/a.json
[5] 绝对路径和相对路径
- path.isAbsolute(path)
其中,path 是一个绝对路径( 比如 'E:/abc' ),或者是以“/”开头的路径,二者都会返回 true
console.log(path.isAbsolute('../testFiles/secLayer'));//false console.log(path.isAbsolute('./join.js'));//false console.log(path.isAbsolute('temp'));//false console.log(path.isAbsolute('/temp/../..'));//true console.log(path.isAbsolute('E:/github/nodeAPI/abc/efg'));//true console.log(path.isAbsolute('///temp123'));//true
- path.relative(from, to) 计算从 from 到 to 的相对路径
console.log(path.relative('C:/test/aaa', 'C:/bbb'));//..\..\bbb console.log(path.relative('C:/test/aaa', 'D:/bbb'));//D:\bbb path.resolve([from ...], to) 解析路径,从源地址 from 到目的地址 to 的绝对路径,类似在 shell 里的 cd 命令 // 解析路径 path.resolve('foo/bar', '/tmp/file/', '..', 'a/../subfile') // 类似于: cd foo/bar cd /tmp/file/ cd .. cd a/../subfile pwd // 拓展:如果某个 from 或 to 参数是绝对路径(比如 'E:/abc',或是以“/”开头的路径),则将忽略之前的from参数 console.log(path.resolve('D:', 'abc', 'D:/a'));//D:\a
综合案例
代码示例:fs 模块和 path 模块的综合运用 — 初始化目录结构
const path = require('path');
const fs = require('fs');
// 存储目录
let root = '/Users/admin/webpiece';
// 文件内容
let fileContent = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="app">
welcome to this page
</div>
</body>
</html>
`;
// 初始化数据
let initData = {
projectName:'mydemo',
data:[{
name:'img',
type:'dir'
},{
name:'css',
type:'dir'
},{
name:'js',
type:'dir'
},{
name:'index.html',
type:'file'
}]
}
// 创建项目根路径
fs.mkdir(path.join(root,initData.projectName),(err)=>{
if(err) return;
// 创建子目录和文件
initData.data.forEach(item=>{
if(item.type == 'dir'){
// 创建子目录
fs.mkdir(path.join(root,initData.projectName,item.name),(err)=>{
// console.log(err);
});
}else if(item.type == 'file'){
// 创建文件并写入内容
fs.writeFile(path.join(root,initData.projectName,item.name),fileContent,(err)=>{
// console.log(err);
});
}
})
})