*遍歷算法*
* * * * *
目錄是一個樹狀結構,在遍歷時一般使用深度優先+先序遍歷算法。深度優先,意味著到達一個節點后,首先接著遍歷子節點而不是鄰居節點。先序遍歷,意味著首次到達了某節點就算遍歷完成,而不是最后一次返回某節點才算數。因此使用這種遍歷方式時,下邊這棵樹的遍歷順序是`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)
}
})
}
```
可以看到,該函數以某個目錄作為遍歷的起點。遇到一個子目錄時,就先接著遍歷子目錄。遇到一個文件時,就把文件的絕對路徑傳給回調函數。回調函數拿到文件路徑后,就可以做各種判斷和處理。因此假設有以下目錄:

*異步遍歷*
* * * * *
如果讀取目錄或讀取文件狀態時使用的是異步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('完成')
})
```
