前言

Node.js 中赋予了 JavaScript 很多在浏览器中没有的能力,譬如:文件读写,创建 http 服务器等等,今天我们就来看看在 node 中怎样用 JavaScript 进行文件的读写操作。

读文件

  1. 我们新建一个hello.txt,并且在里面写入:hello, node.js!! ,如图:

node演示

  1. 我们在hello.txt同级目录下创建一个hello.js文件,我们在这个 js 文件中利用 Node 提供的文件操作 API, 读取hello.txt文件中的内容。
  • node 中对文件相关的操作需要依赖 fs 模块,这个是 node 中内置模块之一,我们需要引入。fs–file system。
1
2
3
4
let fs = require('fs')
fs.readFile()
// 读文件。 readFile函数接受两个参数:读取文件路径,回调函数(error,data两个参数),
读取文件成功:data为文件内容,error为null,读取失败:error为错误对象,data为undefined

最后我们hello.js中的代码如下:

1
2
3
4
5
6
var fs = require('fs')
// 导入文件模块
// node读写文件也有同步和异步的接口
// 同步的方式
var content = fs.readFileSync('hello.txt', { flag: 'r' })
console.log(content.toString())

在这里可以说一下,我们读取回来的默认是二进制的内容,所以需要调用toString()方法进行转换。最后,终端可以看到结果如下:

node演示

1
2
// 你也可以直接加上编码,这样就不需要再调用toString()方法了
var content = fs.readFileSync('hello.txt', { flag: 'r', encoding: 'utf-8' })

上面是采用同步的方式来读取文件,我们也可以使用异步的方式来进行读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 异步
fs.readFile('hello.txt', { flag: 'r', encoding: 'utf-8' }, function (
err,
data
) {
if (err) {
console.log(err)
} else {
console.log(data)
}
console.log('456')
})
console.log('123')

node演示

可以看到我们刚才在hello.txt中写入的文本hello, node.js!!已经打印出来。看到这里是不是觉得很牛叉,JavaScript 居然可以用来读取文件内容,完全颠覆了我们以前对 JavaScript 的理解,然而这一切都得归功于 Node.js。

但是如果我们需要读取多个文件的时候,就会出现一个嵌套回调的问题,我们可以对他进行一个简单的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 封装
function fsRead(path) {
return new Promise(function (resolve, reject) {
fs.readFile(path, { flag: 'r', encoding: 'utf-8' }, function (err, data) {
if (err) {
// 失败执行的内容
reject(err)
} else {
// 成功执行的内容
resolve(data)
}
})
})
}

封装成一个 promise 对象后,我们就可以解决掉回调的问题,也可以直接使用.then 方法。

1
2
var w1 = fsRead('hello.txt')
w1.then((res) => console.log(res))

node演示

我们在读取多个文件的时候也可以这样使用async await 的方法。我们现在有 3 个文件:

文件目录

他们的内容分别是hello:hello2 ,hell2:hello3hello3:我们最终读取的文件, 这样的话我们必须依次读取文件才能读取到最后的文件。

1
2
3
4
5
6
7
async function readList() {
var file2 = await fsRead('hello.txt')
var file3 = await fsRead(file2.trim() + '.txt')
var file3Content = await fsRead(file3.trim() + '.txt')
console.log(file3Content)
}
readList()

node演示

注意:这里我使用了 vscode 编辑器,根据我的代码风格,编辑器每次都会自动在代码末尾添加一行空行,这会导致输出的时候会多出一个空格来,所以我使用了trim()方法去掉空格。

自动换行

写文件

我们新建一个文件write.js中写入下面这行代码,并在同级目录下新建一个test.txt 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let fs = require('fs')
fs.writeFile(
'test.txt',
'今天晚上吃什么',
{ flag: 'w', encoding: 'utf-8' },
function (err) {
if (err) {
console.log('写入出错')
} else {
console.log('写入成功')
}
}
)
// 写文件。writeFile接受三个参数:写入文件路径,写入内容,回调函数。

node演示

写入成功

但是这样的话,我们再继续写入的话,会发现原先的内容会被覆盖掉:

写入覆盖

而我们本来的目的应该是,在后面追加一个内容,这时候我们只需要修改flaga 就好!

1
2
3
4
5
6
7
8
9
10
11
let fs = require('fs')

fs.writeFile('test.txt', '红烧肉', { flag: 'a', encoding: 'utf-8' }, function (
err
) {
if (err) {
console.log('写入出错')
} else {
console.log('写入成功')
}
})

追加内容

但是这也是一个异步的操作,有时候我们也会出现一些问题,不然就必须嵌套,比如我们希望得到的是一个对话,今晚吃啥,红烧肉,红烧肉不好吃:

同步异步问题

但是我们得到的并不是一个我们想要的结果。我们可以进行一个封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function fsWrite(path, content) {
return new Promise(function (resolve, reject) {
fs.writeFile(path, content, { flag: 'a', encoding: 'utf-8' }, function (
err
) {
if (err) {
// console.log('写入出错');
reject(err)
} else {
// console.log('写入成功');
resolve('写入成功')
}
})
})
}

这样我们我们就可以达到之前预期的效果了:

1
2
3
4
5
async function writeList() {
await fsWrite('test.txt', '1:早餐吃什么?\n')
await fsWrite('test.txt', '2:午餐吃什么?\n')
await fsWrite('test.txt', '3:晚餐吃什么?\n')
}

node演示

删除文件

语法

以下为删除文件的语法格式:

1
fs.unlink(path, callback)

参数

参数使用说明如下:

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

实例

input.txt 文件内容为:

1
site: blog.juanertu.com

接下来我们创建 file.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
var fs = require('fs')

console.log('准备删除文件!')
fs.unlink('input.txt', function (err) {
if (err) {
return console.error(err)
}
console.log('文件删除成功!')
})

以上代码执行结果如下:

1
2
3
$ node file.js
准备删除文件!
文件删除成功!

再去查看 input.txt 文件,发现已经不存在了。而且回收站也找不到这个文件,所以没事别瞎删……


创建目录

语法

以下为创建目录的语法格式:

1
fs.mkdir(path[, options], callback)

参数

参数使用说明如下:

  • path - 文件路径。
  • options 参数可以是:
    • recursive - 是否以递归的方式创建目录,默认为 false。
    • mode - 设置目录权限,默认为 0777。
  • callback - 回调函数,没有参数。

实例

代码如下所示:

1
2
3
4
5
6
7
8
9
var fs = require('fs')
// tmp 目录必须存在
console.log('创建目录 /tmp/test/')
fs.mkdir('/tmp/test/', function (err) {
if (err) {
return console.error(err)
}
console.log('目录创建成功。')
})

以上代码执行结果如下:

1
2
$ node index.js
目录创建成功。

可以添加 recursive: true 参数,不管创建的目录 /tmp 和 /tmp/test 是否存在:

1
2
3
fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => {
if (err) throw err
})

node演示

读取目录

语法

以下为读取目录的语法格式:

1
fs.readdir(path, callback)

参数

参数使用说明如下:

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

实例

代码如下所示:

1
2
3
4
5
6
7
8
let fs = require('fs')
fs.readdir('../day02', function (err, files) {
if (err) {
console.log(err)
} else {
console.log(files)
}
})

以上代码执行结果如下:

读取目录

我们还可以进行一些操作:

首先我们新建一个rw.js 文件,里面放着我们之前封装好的读取和写入函数,并导出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
let fs = require('fs')

function fsRead(path) {
return new Promise(function (resolve, reject) {
fs.readFile(path, { flag: 'r', encoding: 'utf-8' }, function (err, data) {
if (err) {
// 失败执行的内容
reject(err)
} else {
// 成功执行的内容
resolve(data)
}
})
})
}

function fsWrite(path, content) {
return new Promise(function (resolve, reject) {
fs.writeFile(path, content, { flag: 'a', encoding: 'utf-8' }, function (
err
) {
if (err) {
// console.log('写入出错');
reject(err)
} else {
// console.log('写入成功');
resolve('写入成功')
}
})
})
}

module.exports = {
fsRead,
fsWrite,
}

我们在index.js 文件中引入,我们要的操作就是读取test文件夹中的文件,并将这些文件的内容全部写入到all.txt 文件中来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let fs = require('fs')
let { fsRead, fsWrite } = require('./rw')

const txtPath = 'all.txt'
fs.readdir('../day02', function (err, files) {
if (err) {
console.log(err)
} else {
console.log(files)
files.forEach(async function (fileName, i) {
let content = await fsRead('../day02/' + fileName)
await fsWrite(txtPath, content)
})
}
})

读取与写入


删除目录

语法

以下为删除目录的语法格式:

1
fs.rmdir(path, callback)

参数

参数使用说明如下:

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

实例

接下来我们创建 file.js 文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var fs = require('fs')
// 执行前创建一个空的 /tmp/test 目录
console.log('准备删除目录 /tmp/test')
fs.rmdir('/tmp/test', function (err) {
if (err) {
return console.error(err)
}
console.log('读取 /tmp 目录')
fs.readdir('/tmp/', function (err, files) {
if (err) {
return console.error(err)
}
files.forEach(function (file) {
console.log(file)
})
})
})

输入输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 引入readline模块
var readline = require('readline')

//创建readline接口实例
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})

// question方法
rl.question('今天晚上吃什么?', function (answer) {
console.log('答案:' + answer)
// 不加close,则程序不会结束
rl.close()
})

// close事件监听
rl.on('close', function () {
// 结束程序
process.exit(0)
})

node演示

这样的话,我们就可以通过提问的方式来创建一个文件:

首先为了避免嵌套的问题,我们还是先对其进行封装一下:

1
2
3
4
5
6
7
function lcQuestion(question) {
return new Promise(function (resolve, reject) {
r1.question(question, function (answer) {
resolve(answer)
})
})
}

然后我们就正式开始操作,比如我准备简历一个 json 文件,包含我的网站信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
let fs = require('fs')
let { fsRead, fsWrite } = require('./rw')
// 导入readline
let readline = require('readline')
//创建readline接口实例
var r1 = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
function lcQuestion(question) {
return new Promise(function (resolve, reject) {
r1.question(question, function (answer) {
resolve(answer)
})
})
}
async function createJson() {
let name = await lcQuestion('您的网站名称?')
let link = await lcQuestion('您的网站地址?')
let description = await lcQuestion('您的网站描述?')
let content = await `{
"name": "${name}",
"link": "${link}",
"description": "${description}"
}`
await fsWrite('site.json', content)
await r1.close()
}
r1.on('close', function () {
// 结束
process.exit(0)
})
createJson()

运行:

node演示

后话

到了这里,我们是不是对 node 有了一个基本的了解,知道 node 是干什么的,而且知道正是由于 node.js,我们的 JavaScript 才有了无限的可能,使得 JavaScript 不单单局限在浏览器窗口,俗话说得好:‘能用 JavaScript 来实现的,最终都会用 JavaScript 来实现’。