提示
具体内容查看 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
# 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
})