*遍历算法*
* * * * *
目录是一个树状结构,在遍历时一般使用深度优先+先序遍历算法。深度优先,意味着到达一个节点后,首先接着遍历子节点而不是邻居节点。先序遍历,意味着首次到达了某节点就算遍历完成,而不是最后一次返回某节点才算数。因此使用这种遍历方式时,下边这棵树的遍历顺序是`A > B > D > E > C > F`。
```
A
/ \
B C
/ \ \
D E F
```
*同步遍历*
* * * * *
了解了必要的算法后,我们可以简单地实现以下目录遍历函数。
```
function travel(dir, callback) {
fs.readdirSync(dir).forEach((file) => {
let pathname = path.join(dir, file)
if (fs.statSync(pathname).isDirectory()) {
travel(pathname, callback)
} else {
callback(pathname)
}
})
}
```
可以看到,该函数以某个目录作为遍历的起点。遇到一个子目录时,就先接着遍历子目录。遇到一个文件时,就把文件的绝对路径传给回调函数。回调函数拿到文件路径后,就可以做各种判断和处理。因此假设有以下目录:
![](https://box.kancloud.cn/d2523ba5cf18d6d160830cdebe28528b_476x465.png)
*异步遍历*
* * * * *
如果读取目录或读取文件状态时使用的是异步API,目录遍历函数实现起来会有些复杂,但原理完全相同。`travel` 函数的异步版本如下。
```
let fs = require('fs')
let path = require('path')
function travel(dir, callback, finish) {
// 调用fs异步API, 接受目录参数和回调函数
fs.readdir(dir, function(err, files) {
// 创建一个自执行函数, files是dir下所有文件
// 传入参数为0, 因为files是一个数组, 包含dir下所有文件的文件名数组
(function next(i) {
console.log(i, '-', dir)
// 进来首先判断当前i是不是完成对dir下所有文件的遍历
if (i < files.length) {
// 取得文件的绝对路径
let pathname = path.join(dir, files[i])
// fs.stat 取得文件信息
fs.stat(pathname, function(err, stats) {
// 判断当前文件是否为文件夹
if (stats.isDirectory()) {
// 如果当前文件是文件夹, 则重新调用travel
// 遵循深度优先原则, 先遍历深度
// 此时finish函数就是next函数 -> function () { next(i + 1) }
// 此时dir就是一个新的文件夹路径, callback不变, 遍历之后调用finish函数
travel(pathname, callback, function() {
next(i + 1)
})
} else {
// 当此文件夹再没有文件夹的时候
// 调用callback函数, 第一个参数是文件的绝对路径
// 第二个参数是next函数, 外面必须调用此函数, 来进行下一步遍历
callback(pathname, function() {
next(i + 1)
})
}
// 注意:每个文件夹都会生成一个next函数
// 而且是自执行的, 所以并不需要控制next函数返回上一层目录
})
// 如果已经完成对dir下所有文件夹里的文件遍历, 即i >= files.length
} else {
finish && finish()
}
})(0)
})
}
travel(path.join(__dirname), function(pathname, next) {
console.log(pathname)
next()
}, function() {
console.log('完成')
})
```
![](https://box.kancloud.cn/6f09566c9969279a74a518a80784fc22_581x252.png)