提示

  具体内容查看 Node 的 API 文档 (opens new window)

无论是前面的学习,还是官网文档中的案例(同时演示了 CJS/ESM 规范两种写法),都表明 Node 也在跟随着时代发展的脚步

# 使用环境 🤔

浏览器中执行(前端)
  • 浏览器中可以借助 webpack 等工具实现对 CommonJS 代码的转换

  • 浏览器本身是支持 ESModule 代码

<script src="./index.js" type="module"></script>
Node 环境中执行 ✍(后端)

  乖乖的使用 CommonJS 规范吧 😂

  实在不爽,使用 node v13.2.0 之后的版本,并进行如下操作:

{
  // 在package.json加入
  "type": "module"
}

# 一、path 模块

webpack 中的绝对路径
module.exports = {
  output: {
    path: path.join(__dirname, './dist'),
    filename: 'bundle.js'
  },
  resolve: {
    extensions: ['.js','.json']
    alias: {
      '@': path.join(__dirname, './src/')
    }
  }
}
const resolve = dir => path.resolve(__dirname, dir)

module.exports = {
  output: {
    path: resolve('dist'),
    filename: 'bundle.js'
  },
  resolve: {
    extensions: ['.js','.json']
    alias: {
      '@': resolve('src')
    }
  }
}

# 1、文件路径

  • index.js
const path = require('path')

filePath = '/ren/note-taking/README.md'

// 1、查看方法
// console.log(path)

// 2、解析文件路径
console.log(path.parse(filePath))

运行结果:

{
  root: '/',
  dir: '/ren/note-taking',  # 等效于:path.dirname(filePath)
  base: 'README.md',  # 等效于:path.basename(filePath)
  ext: '.md',  # 等效于:path.extname(filePath)
  name: 'README' 🤔
}

# 2、绝对路径 ✨

关于 path.resolve( ) 原理

官方:

https://nodejs.cn/api/path.html#pathresolvepaths

简述:

  • resolve 是按照给定路径的序列从右到左进行处理,直到可以构造成一个绝对路径
  • 如果处理完给定路径后,还没有生成绝对路径,则使用当前工作目录
  • path.resolve()

resolve 找根 /

const path = require('path')

// 从右到左
path.resolve('/root', '/mydir', './one', 'myfile.txt') // C://mydir/one/myfile.txt

// 当前工作目录
path.resolve('index.js') // C://Users/Desktop/code-case/index.js

# 3、路径拼接

path.join( ) 和 path.resolve( ) 的联系
const path = require('path')

console.log(__dirname)
// C://Users/Desktop/code-case/    ——> 末尾有斜杠

// 两者关联
path.join(__dirname, 'src') === path.resolve(__dirname, 'src')
  • path.join

join 处理 .././、``

const path = require('path')

path.join('/root', '/mydir', '../one', 'myfile.txt')
// /root/one/myfile.txt

path.join('/root/mydir', '../one/myfile.txt')
// /root/one/myfile.txt

# 4、路径优化

  • path.normalize
const path = require('path')

path.normalize('../../src/../src/node')
// '../../src/node'

# 二、url 模块

path.parse( ) 和 url.parse( ) 的区别

path 模块:

用于操作文件路径,包括路径分隔符、扩展名、绝对路径、相对路径等信息

url 模块:

用于解析和格式化 URL 地址,包括查询参数、协议、主机名、路径等信息

# 准备工作

  要向对 url 地址进行操作,首先得基于 node 搭建一个可供操作的 web 服务器:

示例地址:http://localhost:6061/problems/?category_id=2&sort=students_count

代码示例
  • index.html
<body>
  <h4>1、请求方式</h4>
  <button class="btn-axios">发起axios请求</button>

  <h4>2、服务器地址</h4>
  <p>
    node原生-http服务器:<br /><br />http://localhost:6061/problems/?category_id=2&sort=students_count
  </p>

  <script>
    document.querySelector('.btn-axios').onclick = function () {
      axios.get('http://localhost:6061/problems?category_id=2&sort=students_count').then((res) => {
        console.log(res.data)
      })
    }
  </script>
</body>
  • index.js
const http = require('http')

const server = http.createServer()

server.on('request', (req, res) => {
  res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8081')

  // 代码逻辑

  res.end(JSON.stringify({ code: 1, message: 'success!' }))
})

server.listen(6061, () => {
  console.log('Server running at http://localhost:6061')
})

  这样,我们就可以在上述示例代码的代码逻辑部分进行学习了 😂。

# 1、地址拼接 ✨

  注意不要把这里的 path.resolve() 和 后面的 url.resolve() 搞混了

  • path.resolve() 用于根据当前操作系统的规则解析传入的路径,并返回绝对路径
  • url.resolve() 则是用于解析 URL 中的相对路径

总结

  若后者带 / ,则会替换到前者的 整个路由地址

var url = require('url')

// 1、前者末尾存在 '/'
// 拼接
url.resolve('/one/two/', 'three') // '/one/two/three'
url.resolve('http://example.com/one/two/', 'three') // 'http://example.com/one/two/three'
// 替换整个路由地址
url.resolve('/one/two/', '/three') // '/three'
url.resolve('http://example.com/one/two/', '/three') // 'http://example.com/three'

// 2、前者末尾没有 '/'
// 替换末尾路径
url.resolve('/one/two', 'three') // '/one/three'
url.resolve('http://example.com/one/two', 'three') // 'http://example.com/one/three'
// 替换整个路由地址
url.resolve('/one/two', '/three') // '/three'
url.resolve('http://example.com/one/two', '/three') // 'http://example.com/three'

# 2、url 类

  不同于旧版本的 query,这里的 searchParams 是一个可迭代对象,可以使用searchParams.get('category_id')方式获取数据

URL 类 与 绝对路径 🎈
  • CJS 规范
const { resolve } = require('node:path')

const filePath = resolve('./data.json')
  • ESM 规范

在 vite 脚手架项目中会经常看到

const filePath = new URL('.data.json', import.meta.url)
const { createServer } = require('http')

const server = createServer()
server.on('request', (req, res) => {
  // 解析完整请求地址
  const urlObj = new URL(req.url, 'http://' + req.headers['host'])
  const { pathname, searchParams } = urlObj

  // 1、url路径
  console.log(pathname) // /problems/
  // 2、查询参数
  console.log(searchParams) // 可迭代✍对象 { 'category_id' => '2', 'sort' => 'students_count' }

  res.end()
})
server.listen(6061)

# 3、url 对象

  默认情况下,经过 url.parse 后的 query 属性值是一个以&分隔的键值对字符串,但是如果将第二个参数设置为 true,则会将其解析为一个对象。

const { createServer } = require('http')
const { parse } = require('url')

const server = createServer()
server.on('request', (req, res) => {
  // 解析请求地址 👀
  const url = req.url // /problems?category_id=2&sort=students_count
  const { pathname, query } = parse(url, true)

  // 1、url路径
  console.log(pathname) // /problems
  // 2、查询参数
  console.log(query) // 对象 { category_id: '2', sort: 'students_count' }

  res.end()
})
server.listen(6061)

# 拓展:querystring

  前面我们接触的 路径参数 的形式有默认的字符串形式、JSON 数据格式、迭代对象形式。

querystring 模块同样可以对路径参数部分进行处理

使用示例
const querystring = require('querystring')

server.on('request', (req, res) => {
  // 1、parse方法✨
  const queryStr1 = 'name=lencamo&age=20'

  const queryObj1 = querystring.parse(queryStr1)
  console.log(queryObj1) // { name: 'lencamo', age: '20' }

  // 2、stringify方法✨
  const queryObj2 = {
    name: 'lencamo',
    age: '20'
  }
  const queryStr2 = querystring.stringify(queryObj2)
  console.log(queryStr2) // name=lencamo&age=20

  // 3、百分比编码(安全性角度)
  const queryStr = 'name=字母哥&age=26'

  const escaped = querystring.escape(queryStr)
  console.log(escaped) // name%3D%E5%AD%97%E6%AF%8D%E5%93%A5%26age%3D26

  const unescaped = querystring.unescape(escaped)
  console.log(unescaped) // name=字母哥&age=26

  res.end()
})

# 三、fs 模块

# 目录结构

文件系统
├─ Promise示例
├─ 回调示例
├─ 同步示例
├─ Promises API 👈
|   └─ readFile()
├─ 回调 API 👈
│   ├─ readFile()
│   ├─ createReadStream()
│   ├─ open()
|   └─ writeFile()
├─ 同步 API
|   └─ readFileSync()
├─ 注意事项
│   ├─ 文件系统标志
|   └─ 文件描述符
└─ ……

# 1、读取文件

  • fs.readFileSync():同步读取

如果指定了该 encoding 选项,则此函数返回一个字符串。否则,它将返回 buffer(二进制数据)。

// import { readFileSync } from 'node:fs'
const { readFileSync } = require('fs')

try {
  const data = readFileSync('./data.txt', { encoding: 'utf8' })
  console.log(data)
} catch (err) {
  console.error(err)
}
  • fs.readFile():异步读取(回调函数)
// import { readFile } from 'node:fs'
const { readFile } = require('fs')

readFile('./data.txt', { encoding: 'utf8' }, (err, data) => {
  if (err) throw err
  console.log(data)
})
  • fs.promises.readFile():异步读取(Promise)

Node.js v10.0 及以上版本支持

// import { readFile } from 'node:fs/promises'
const { readFile } = require('fs').promises

readFile('./data.txt', { encoding: 'utf8' })
  .then((data) => {
    console.log(data)
  })
  .catch((err) => {
    console.error(err)
  })
拓展:createReadStream() 流读取
  • fs.createReadStream():以流的方式读取文件内容
// import { createReadStream } from 'node:fs'
const { createReadStream } = require('fs')

const readStream = createReadStream('./data.txt', { encoding: 'utf8' })

// console.log(readStream) // ReadStream 对象
readStream.on('data', (chunk) => {
  console.log(chunk)
})
readStream.on('end', () => {
  console.log('Finished reading file.')
})
readStream.on('error', (err) => {
  console.error(err)
})

# 2、文件描述符 fd

  在常见的操作系统中,每个打开(open)的文件都被分配了一个称为文件描述符的简单的数字标识符,用于对特定的文件进行跟踪。

基于 fs.open() 的函数也表现出这种行为: fs.writeFile()、fs.readFile() 等

// import { open } from 'node:fs'
const { open, close, fstat, readFile } = require('fs')

open('./data.txt', (err, fd) => {
  if (err) throw err

  console.log(fd) // 文件描述符

  fstat(fd, (err, state) => {
    if (err) throw err
    console.log(state) // 文件信息

    close(fd)
  })

  // readFile(fd, { encoding: 'utf8' }, (err, data) => {
  //   if (err) throw err
  //   console.log(data)
  // })
})

# 3、写入文件

  要注意的是,写入文件必须要追加一个 err 回调,不然会报错。

具体查看fs.writeFile(file, data[, options], callback) (opens new window)

const { writeFile } = require('fs')

const data = '\nHello Node.js'

// 没有就创建该文件
writeFile('./bundle.txt', data, { encoding: 'utf8', flag: 'a' }, (err) => {
  if (err) throw err
  console.log('追加(append)写入成功')
})

# 4、文件系统标志 flag

具体查看File system flags (opens new window)

# 5、文件夹操作

  • 创建 mkdir
const { mkdir } = require('fs')

// 创建./docs/.vuepress/dist,无论 ./docs 和 ./docs/.vuepress 是否存在
mkdir('./docs/.vuepress/dist', { recursive: true }, (err, path) => {
  if (err) throw err

  console.log(path === undefined)
})
  • 读取 ✨ readdir

和前面的 readFile 一样,它同样有 readdirSync()、readdir()等版本

递归封装
// 递归封装
function readDirectory(path) {
  // withFileTypes:结果将包含 <fs.Dirent> 对象
  readdir(path, { withFileTypes: true }, (err, files) => {
    files.forEach((item) => {
      if (item.isDirectory()) {
        readDirectory(`${path}/${item.name}`)
      } else {
        console.log('获取的文件:', item.name)
      }
    })
  })
}
readDirectory('./myWorkSpace')

提示

  默认情况下只会获取文件的 name,如果我们还想获取文件/文件夹的更多信息,可以设置 option: { withFileTypes: true },其会返回一个 <fs.Dirent> (opens new window) 对象,该对象包含 name、isDirectory 等信息

const { readdir } = require('fs')

// 本质是是获取文件/文件夹名
// 1、同步
readdirSync(__dirname).forEach((item) => {
  console.log('path路径下有文件/文件夹:', item)
})

// 2、异步
readdir(__dirname, { withFileTypes: true }, (err, files) => {
  files.forEach((item) => {
    console.log('path路径下有文件/文件夹:', item.name)
  })
})
  • 重命名 rename
const { rename } = require('fs')

// 本质是是重命名路径名
rename('./myWorkSpace', './lencamo_space', (err) => {
  if (err) throw err
})
rename('./myWorkSpace/aaa.txt', './lencamo_space/bbb.txt', (err) => {
  if (err) throw err
})
更新于 : 8/7/2024, 2:16:31 PM