闭包在递归中的应用

104 阅读2分钟

以前面试时被问过闭包的内容,什么是闭包,以及闭包的实际应用场景。简单的说,闭包是能访问另一作用域变量的函数。比如:

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)

此时,这种使用方式是成立的。