以前面试时被问过闭包的内容,什么是闭包,以及闭包的实际应用场景。简单的说,闭包是能访问另一作用域变量的函数。比如:
function A() {
const p1 = ''
function B() {
console.log(p1)
}
}
function C() {
console.log(p1)
}
上述示例中,p1 是函数 A 的作用域变量, 函数 B 可以访问 p1,那么 B 就是 A 的一个闭包,B 被 A 封闭包裹起来了。函数 C 没有被 A 封闭包裹,C 就不是 A 的闭包,C 访问不了 p1。
那么闭包在递归中有什么作用呢?这是我在项目实际遇到的场景。
我需要写一个 moveFiles 的函数,实现移动文件的功能,伪代码如下:
// 将 srcDir 中的所有文件移动到 destDir 中
async function moveFiles(srcDir, destDir) {
const files = await fs.readdir(srcDir)
for (const file of files) {
const filePath = path.join(srcDir, file)
const isFileType = await isFile(filePath)
if (isFileType) {
const destPath = path.join(destDir, file)
await copyFile(filePath, destPath)
await unlink(filePath)
continue
}
await moveFiles(filePath, path.join(destDir, path.basename(filePath)))
}
}
大概意思就是先读取 srcDir 中的文件,如果是文件就移动到 destDir 中,如果是目录就继续调用 moveFiles,递归地将 srcDir 的所有子目录、孙目录等等也照搬到 destDir 中。
此时就有一个问题,我希望最后移动完毕,有一个返回值,这个返回值是一个数组,记录了总共移动了哪些文件,不用闭包的话实现如下:
// 全局变量,记录移动后的文件
const destFiles = []
async function moveFiles(srcDir, destDir) {
const files = ...
for (const file of files) {
...
if (isFileType) {
...
await copyFile(filePath, destPath)
// 每次移动后的路径保存下来
destFiles.push(destPath)
await unlink(filePath)
continue
}
await moveFiles(filePath, path.join(destDir, path.basename(filePath)))
}
}
// 递归调用完,destFiles 中包含了所有移动后的文件路径
这样就有个问题,函数毫无封装性可言,你没办法当作一个工具来使用,比如:
const destFiles = await fileUtil.moveFiles(srcDir, destDir)
这样是行不通的。
下面就用闭包改造一下:
async function moveFiles(srcFolder, destFolder) {
const destFiles = []
async function doMove(srcDir, destDir) {
const files = ...
for (const file of files) {
...
if (isFileType) {
...
await copyFile(filePath, destPath)
// 每次移动后的路径保存下来
destFiles.push(destPath)
await unlink(filePath)
continue
}
await doMove(filePath, path.join(destDir, path.basename(filePath)))
}
}
await doMove(srcFolder, destFolder)
return destFiles
}
看明白了吗?doMove 是 moveFiles 的闭包,它访问了 moveFiles 中的 destFiles,最后 moveFiles 的返回值是 destFiles,记录了所有移动后的文件路径,这样就可以当作一个工具方法来使用:
const destFiles = await fileUtil.moveFiles(srcDir, destDir)
此时,这种使用方式是成立的。