您现在的位置是:网站首页 > 目录操作文章详情

目录操作

Node.js 提供了丰富的文件系统操作能力,其中目录操作是核心功能之一。通过 fs 模块,开发者可以轻松实现目录的创建、删除、遍历等操作,同时结合异步和同步方法满足不同场景需求。

目录的创建

在 Node.js 中,创建目录主要通过 fs.mkdirfs.mkdirSync 实现。异步方法 fs.mkdir 接受回调函数,而同步方法 fs.mkdirSync 会阻塞代码执行直到操作完成。

const fs = require('fs');

// 异步创建目录
fs.mkdir('./newDir', (err) => {
  if (err) throw err;
  console.log('目录创建成功');
});

// 同步创建目录
try {
  fs.mkdirSync('./newDirSync');
  console.log('目录创建成功');
} catch (err) {
  console.error(err);
}

如果需要创建多级目录(即递归创建),可以传递 { recursive: true } 选项:

// 异步递归创建目录
fs.mkdir('./parent/child/grandchild', { recursive: true }, (err) => {
  if (err) throw err;
  console.log('多级目录创建成功');
});

目录的删除

删除目录使用 fs.rmdirfs.rmdirSync。注意:目录必须为空才能被删除,否则会抛出错误。

// 异步删除空目录
fs.rmdir('./emptyDir', (err) => {
  if (err) throw err;
  console.log('目录删除成功');
});

// 同步删除空目录
try {
  fs.rmdirSync('./emptyDirSync');
  console.log('目录删除成功');
} catch (err) {
  console.error(err);
}

对于非空目录,可以使用 fs.rm 或第三方库如 rimraf

// 使用 fs.rm 删除非空目录(Node.js 14.14.0+)
fs.rm('./nonEmptyDir', { recursive: true, force: true }, (err) => {
  if (err) throw err;
  console.log('非空目录删除成功');
});

目录的读取

读取目录内容使用 fs.readdirfs.readdirSync,返回目录中的文件和子目录名称数组。

// 异步读取目录
fs.readdir('./targetDir', (err, files) => {
  if (err) throw err;
  console.log('目录内容:', files); // 输出: ['file1.txt', 'subdir', 'app.js']
});

// 同步读取目录
try {
  const files = fs.readdirSync('./targetDir');
  console.log('目录内容:', files);
} catch (err) {
  console.error(err);
}

可以通过 withFileTypes: true 选项获取更详细的信息:

fs.readdir('./targetDir', { withFileTypes: true }, (err, entries) => {
  if (err) throw err;
  entries.forEach(entry => {
    console.log(
      `${entry.name} - ${entry.isDirectory() ? '目录' : '文件'}`
    );
  });
});

目录的遍历

递归遍历目录是常见需求,以下示例展示如何实现深度优先遍历:

const path = require('path');

function traverseDir(dirPath) {
  const files = fs.readdirSync(dirPath);
  files.forEach(file => {
    const fullPath = path.join(dirPath, file);
    const stat = fs.statSync(fullPath);
    if (stat.isDirectory()) {
      console.log(`进入目录: ${fullPath}`);
      traverseDir(fullPath); // 递归调用
    } else {
      console.log(`文件: ${fullPath}`);
    }
  });
}

traverseDir('./project');

对于异步遍历,可以使用 fs.promises API:

async function asyncTraverseDir(dirPath) {
  const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
  for (const entry of entries) {
    const fullPath = path.join(dirPath, entry.name);
    if (entry.isDirectory()) {
      await asyncTraverseDir(fullPath);
    } else {
      console.log(`文件: ${fullPath}`);
    }
  }
}

asyncTraverseDir('./project').catch(console.error);

目录的监控

使用 fs.watch 可以监控目录变化,但需要注意不同平台的差异性:

const watcher = fs.watch('./watchDir', (eventType, filename) => {
  console.log(`事件类型: ${eventType}, 文件: ${filename}`);
});

// 10秒后关闭监控
setTimeout(() => {
  watcher.close();
  console.log('停止目录监控');
}, 10000);

更稳定的方案是使用 chokidar 第三方库:

const chokidar = require('chokidar');

const watcher = chokidar.watch('./watchDir', {
  ignored: /(^|[\/\\])\../, // 忽略隐藏文件
  persistent: true
});

watcher
  .on('add', path => console.log(`文件添加: ${path}`))
  .on('change', path => console.log(`文件修改: ${path}`))
  .on('unlink', path => console.log(`文件删除: ${path}`));

目录路径操作

Node.js 的 path 模块专门处理路径相关操作,以下是一些常见用法:

const path = require('path');

// 路径拼接
const fullPath = path.join(__dirname, 'files', 'data.json');
console.log(fullPath); // 输出完整路径

// 获取路径的目录名
console.log(path.dirname(fullPath)); // 输出目录部分

// 获取路径的文件名
console.log(path.basename(fullPath)); // 输出: data.json
console.log(path.basename(fullPath, '.json')); // 输出: data

// 获取扩展名
console.log(path.extname(fullPath)); // 输出: .json

// 路径解析
console.log(path.parse(fullPath));
/* 输出:
{
  root: '/',
  dir: '/home/user/files',
  base: 'data.json',
  ext: '.json',
  name: 'data'
}
*/

临时目录处理

处理临时目录时,可以使用 os.tmpdir() 获取系统临时目录路径:

const os = require('os');
const tempDir = os.tmpdir();
console.log(`系统临时目录: ${tempDir}`);

// 创建临时目录示例
const tempDirPath = path.join(tempDir, `myapp-${Date.now()}`);
fs.mkdirSync(tempDirPath);
console.log(`临时目录创建于: ${tempDirPath}`);

// 使用后清理
process.on('exit', () => {
  fs.rmSync(tempDirPath, { recursive: true });
});

目录权限检查

检查目录是否存在及其权限可以使用 fs.accessfs.accessSync

// 异步检查目录是否存在并可写
fs.access('./targetDir', fs.constants.F_OK | fs.constants.W_OK, (err) => {
  if (err) {
    console.error('目录不存在或不可写');
  } else {
    console.log('目录存在且可写');
  }
});

// 同步版本
try {
  fs.accessSync('./targetDir', fs.constants.R_OK);
  console.log('目录可读');
} catch (err) {
  console.error('目录不可读');
}

目录操作的实际应用

一个常见的应用场景是项目初始化时创建目录结构:

const projectStructure = [
  'src/',
  'src/components/',
  'src/styles/',
  'public/',
  'public/images/',
  'config/'
];

function initProject(basePath) {
  projectStructure.forEach(dir => {
    const dirPath = path.join(basePath, dir);
    try {
      fs.mkdirSync(dirPath, { recursive: true });
      console.log(`创建目录: ${dirPath}`);
    } catch (err) {
      console.error(`创建目录失败: ${dirPath}`, err);
    }
  });
}

initProject('./my-project');

另一个场景是清理构建产物:

function cleanBuild() {
  const buildDirs = ['dist/', 'build/', '.cache/'];
  
  buildDirs.forEach(dir => {
    if (fs.existsSync(dir)) {
      fs.rmSync(dir, { recursive: true, force: true });
      console.log(`已删除: ${dir}`);
    }
  });
}

cleanBuild();

目录操作的性能考虑

处理大量文件时,同步操作会阻塞事件循环,影响性能。此时应该:

  1. 使用异步方法
  2. 限制并发操作数量
  3. 考虑使用工作线程

以下示例展示如何使用队列控制并发:

const { EventEmitter } = require('events');

class DirProcessor extends EventEmitter {
  constructor(concurrency = 4) {
    super();
    this.concurrency = concurrency;
    this.queue = [];
    this.processing = 0;
  }

  addTask(task) {
    this.queue.push(task);
    this.process();
  }

  process() {
    while (this.processing < this.concurrency && this.queue.length) {
      const task = this.queue.shift();
      this.processing++;
      
      task(() => {
        this.processing--;
        this.process();
        if (this.processing === 0 && this.queue.length === 0) {
          this.emit('done');
        }
      });
    }
  }
}

// 使用示例
const processor = new DirProcessor(2);
const dirs = ['dir1', 'dir2', 'dir3', 'dir4', 'dir5'];

dirs.forEach(dir => {
  processor.addTask(done => {
    fs.readdir(dir, (err, files) => {
      if (err) return console.error(err);
      console.log(`${dir} 包含 ${files.length} 个文件`);
      done();
    });
  });
});

processor.on('done', () => console.log('所有目录处理完成'));

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步