内置模块之 path
- 内置模块, require 之后直接使用
- 用于处理文件/目录的路径
path 模块常见 API
- basename() 获取路径中基础名称
- 返回的就是接收路径当中的最后一部分
- dirname()获取路径中目录名称
- 返回路径中最后一个部分的上一层目录所在路径
- extname() 获取路径中扩展名称
- 返回 path路径中相应文件的后缀名
- 如果 path 路径当中存在多个点,它匹配的是最后一个点,到结尾的内容
- isAbsolute() 获取路径是否为绝对路径
- join()拼接多个路径片段
- resolve() 返回绝对路径
- parse() 解析路径
- 接收一个路径,返回一个对象,包含不同的信息
- root、dir、base、ext、name
- format() 序列化路径
- normalize() 规范化路径
全局变量之 Buffer
- Buffer 让 JavaScript 可以操作二进制
- Nodejs 中 Buffer 是一片内存空间
- Buffer 是 Nodejs 的内置类
- 总结
- 无需 require 的一个全局变量
- 实现 Nodejs 平台下的二进制数据操作
- 不占据 V8 堆内存大小的内存空间
- 内存的使用由 Node 来控制, 由 V8 的 GC 回收
- 一般配合 Steam 流使用, 充当数据缓冲区
创建 Buffer 实例
- alloc:创建指定字节大小的 buffer
- allocUnsafe: 创建指定大小的 buffer (不安全)
- from: 接收数据, 创建 buffer
Buffer 实例方法
- fill: 使用数据填充 buffer
- write:向 buffer 中写入数据
- toString: 从buffer 中提取数据
- slice: 截取 buffer
- indexOf: 在 buffer 中查找数据
- copy: 拷贝 buffer 中的数据
let buf = Buffer.alloc(6)
// fill
/* buf.fill(123)
console.log(buf)
console.log(buf.toString()) */
// write
/* buf.write('123', 1, 4)
console.log(buf)
console.log(buf.toString()) */
// toString
/* buf = Buffer.from('拉勾教育')
console.log(buf)
console.log(buf.toString('utf-8', 3, 9)) */
// slice
/* buf = Buffer.from('拉勾教育')
let b1 = buf.slice(-3)
console.log(b1)
console.log(b1.toString()) */
// indexOf
/* buf = Buffer.from('zce爱前端,爱拉勾教育,爱大家,我爱所有')
console.log(buf)
console.log(buf.indexOf('爱qc', 4)) */
// copy
let b1 = Buffer.alloc(6)
let b2 = Buffer.from('拉勾')
b2.copy(b1, 3, 3, 6)
console.log(b1.toString())
console.log(b2.toString())
Buffer 静态方法
- concat: 将多个 buffer 拼接成一个新的 buffer
- isBuffer: 判断当前数据是否为 Buffer ```typescript /* let b1 = Buffer.from(‘拉勾’) let b2 = Buffer.from(‘教育’)
let b = Buffer.concat([b1, b2], 9) console.log(b) console.log(b.toString()) 拉勾教 */
// isBuffer let b1 = ‘123’ console.log(Buffer.isBuffer(b1))
<a name="QrPTV"></a>
### 自定义 Buffer 之 split
```typescript
ArrayBuffer.prototype.split = function (sep) {
let len = Buffer.from(sep).length
let ret = []
let start = 0
let offset = 0
while( offset = this.indexOf(sep, start) !== -1) {
ret.push(this.slice(start, offset))
start = offset + len
}
ret.push(this.slice(start))
return ret
}
let buf = 'zce吃馒头,吃面条,我吃所有吃'
let bufArr = buf.split('吃')
console.log(bufArr)
FS 模块
- FS 是内置核心模块, 提供文件系统操作的 API
常见 flag 操作符
- r: 表示可读
- w: 表示可写
- s:表示同步
- +:表示执行相反操作
- x:表示排它操作
- a:表示追加操作
fd 就是操作系统分配给被打开文件的标识
fs 介绍总结
- fs 是 Nodejs 中内置核心模块
- 代码层面上 fs 分为基本操作类和常用 API
-
文件读写与拷贝操作
文件操作 API
readFile: 从指定文件中读取数据
- writeFile: 向指定文件中写入数据
- 会覆盖原文件内容
- 如果没有指定文件、会自动创建一个
- appendFile: 追加的方式向指定文件中写入数据
- copyFile: 将某个文件中的数据拷贝至另一文件
- watchFile: 对指定文件进行监控
- **unwatchFile: 取消监控
**
const fs = require('fs')
const path = require('path')
// readFile
/* fs.readFile(path.resolve('data1.txt'), 'utf-8', (err, data) => {
console.log(err)
if (!null) {
console.log(data)
}
}) */
// writeFile
/* fs.writeFile('data.txt', '123', {
mode: 438,
flag: 'w+',
encoding: 'utf-8'
}, (err) => {
if (!err) {
fs.readFile('data.txt', 'utf-8', (err, data) => {
console.log(data)
})
}
}) */
// appendFile
/* fs.appendFile('data.txt', 'hello node.js',{}, (err) => {
console.log('写入成功')
}) */
// copyFile, 第一个是被拷贝文件 第二个是拷贝文件
/* fs.copyFile('data.txt', 'test.txt', () => {
console.log('拷贝成功')
}) */
// watchFile
fs.watchFile('data.txt', {interval: 20}, (curr, prev) => {
// curr 表示修改之后的文件 prev 表示修改之前的文件
if (curr.mtime !== prev.mtime) {
console.log('文件被修改了')
fs.unwatchFile('data.txt')
}
})
文件操作实现 md 转 html
const fs = require('fs')
const path = require('path')
const marked = require('marked')
const browserSync = require('browser-sync')
/**
* 01 读取 md 和 css 内容
* 02 将上述读取出来的内容替换占位符,生成一个最终需要展的 Html 字符串
* 03 将上述的 Html 字符写入到指定的 Html 文件中
* 04 监听 md 文档内容的变经,然后更新 html 内容
* 05 使用 browser-sync 来实时显示 Html 内容
*/
let mdPath = path.join(__dirname, process.argv[2])
let cssPath = path.resolve('github.css')
let htmlPath = mdPath.replace(path.extname(mdPath), '.html')
fs.watchFile(mdPath, (curr, prev) => {
if (curr.mtime !== prev.mtime) {
fs.readFile(mdPath, 'utf-8', (err, data) => {
// 将 md--》html
let htmlStr = marked(data)
fs.readFile(cssPath, 'utf-8', (err, data) => {
let retHtml = temp.replace('{{content}}', htmlStr).replace('{{style}}', data)
// 将上述的内容写入到指定的 html 文件中,用于在浏览器里进行展示
fs.writeFile(htmlPath, retHtml, (err) => {
console.log('html 生成成功了')
})
})
})
}
})
browserSync.init({
browser: '',
server: __dirname,
watch: true,
index: path.basename(htmlPath)
})
const temp = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<style>
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 1000px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 750px) {
.markdown-body {
padding: 15px;
}
}
{{style}}
</style>
</head>
<body>
<div class="markdown-body">
{{content}}
</div>
</body>
</html>
`
文件打开与关闭
- fs.open
- fs.close ```typescript const fs = require(‘fs’) const path = require(‘path’)
// open fs.open(path.resolve(‘data.txt’), ‘r’, (err, fd) => { console.log(fd) })
// close fs.open(‘data.txt’, ‘r’, (err, fd) => { console.log(fd) fs.close(fd, err => { console.log(‘关闭成功’) }) })
<a name="nKbwD"></a>
### 大文件读写操作
- ![image.png](https://cdn.nlark.com/yuque/0/2022/png/245377/1659970846744-fa8f0f01-063a-4541-a30a-9edfad78deab.png#clientId=u38ebfc24-b645-4&from=paste&height=448&id=ue6c4ba22&originHeight=895&originWidth=1589&originalType=binary&ratio=1&rotation=0&showTitle=false&size=160483&status=done&style=none&taskId=uf3dabb86-583a-4fa8-b690-291dc68fb8f&title=&width=794.5)
- 一次性读写大文件时,内存有限,通过Buffer 搬运
- 读: 把磁盘里的内容读出来,放到Buffer 暂存区中
- 写: 把Buffer 暂存区中的内容写入到磁盘文件中
```typescript
const fs = require('fs')
// read : 所谓的读操作就是将数据从磁盘文件中写入到 buffer 中
let buf = Buffer.alloc(10)
/**
* fd 定位当前被打开的文件
* buf 用于表示当前缓冲区
* offset 表示当前从 buf 的哪个位置开始执行写入
* length 表示当前次写入的长度
* position 表示当前从文件的哪个位置开始读取
*/
/* fs.open('data.txt', 'r', (err, rfd) => {
console.log(rfd)
fs.read(rfd, buf, 1, 4, 3, (err, readBytes, data) => {
console.log(readBytes)
console.log(data)
console.log(data.toString())
})
}) */
// write 将缓冲区里的内容写入到磁盘文件中
buf = Buffer.from('1234567890')
fs.open('b.txt', 'w', (err, wfd) => {
fs.write(wfd, buf, 2, 4, 0, (err, written, buffer) => {
console.log(written, '----')
fs.close(wfd)
})
})
文件拷贝自定义实现
const fs = require('fs')
/**
* 01 打开 a 文件,利用 read 将数据保存到 buffer 暂存起来
* 02 打开 b 文件,利用 write 将 buffer 中数据写入到 b 文件中
*/
let buf = Buffer.alloc(10)
// 01 打开指定的文件
/* fs.open('a.txt', 'r', (err, rfd) => {
// 03 打开 b 文件,用于执行数据写入操作
fs.open('b.txt', 'w', (err, wfd) => {
// 02 从打开的文件中读取数据
fs.read(rfd, buf, 0, 10, 0, (err, readBytes) => {
// 04 将 buffer 中的数据写入到 b.txt 当中
fs.write(wfd, buf, 0, 10, 0, (err, written) => {
console.log('写入成功')
})
})
})
}) */
// 02 数据的完全拷贝
/* fs.open('a.txt', 'r', (err, rfd) => {
fs.open('b.txt', 'a+', (err, wfd) => {
fs.read(rfd, buf, 0, 10, 0, (err, readBytes) => {
fs.write(wfd, buf, 0, 10, 0, (err, written) => {
fs.read(rfd, buf, 0, 5, 10, (err, readBytes) => {
fs.write(wfd, buf, 0, 5, 10, (err, written) => {
console.log('写入成功')
})
})
})
})
})
}) */
const BUFFER_SIZE = buf.length
let readOffset = 0
fs.open('a.txt', 'r', (err, rfd) => {
fs.open('b.txt', 'w', (err, wfd) => {
function next () {
fs.read(rfd, buf, 0, BUFFER_SIZE, readOffset, (err, readBytes) => {
if (!readBytes) {
// 如果条件成立,说明内容已经读取完毕
fs.close(rfd, ()=> {})
fs.close(wfd, ()=> {})
console.log('拷贝完成')
return
}
readOffset += readBytes
fs.write(wfd, buf, 0, readBytes, (err, written) => {
next()
})
})
}
next()
})
})
常见目录操作 API
- access: 判断文件或目录是否具有操作权限
- stat:获取目录文件信息
- mkdir: 创建目录
- rmdir: 删除目录
- readdir: 读取目录中内容
- unlink: 删除指定文件 ```typescript const fs = require(‘fs’)
// 一、access / fs.access(‘a.txt’, (err) => { if (err) { console.log(err) } else { console.log(‘有操作权限’) } }) /
// 二、stat / fs.stat(‘a.txt’, (err, statObj) => { console.log(statObj.size) console.log(statObj.isFile()) console.log(statObj.isDirectory()) }) /
// 三、mkdir / fs.mkdir(‘a/b/c’, {recursive: true}, (err) => { if (!err) { console.log(‘创建成功’) }else{ console.log(err) } }) /
// 四、rmdir fs.rmdir(‘a’, {recursive: true}, (err) => { if (!err) { console.log(‘删除成功’) } else { console.log(err) } })
// 五、readdir / fs.readdir(‘a/b’, (err, files) => { console.log(files) }) /
// 六、unlink / fs.unlink(‘a/a.txt’, (err) => { if (!err) { console.log(‘删除成功’) } }) /
<a name="JJvw7"></a>
### 创建目录之同步实现
```typescript
const fs = require('fs')
const path = require('path')
/**
* 01 将来调用时需要接收类似于 a/b/c ,这样的路径,它们之间是采用 / 去行连接
* 02 利用 / 分割符将路径进行拆分,将每一项放入一个数组中进行管理 ['a', 'b', 'c']
* 03 对上述的数组进行遍历,我们需要拿到每一项,然后与前一项进行拼接 /
* 04 判断一个当前对拼接之后的路径是否具有可操作的权限,如果有则证明存在,否则的话就需要执行创建
*/
function makeDirSync (dirPath) {
let items = dirPath.split(path.sep)
for(let i = 1; i <= items.length; i++) {
let dir = items.slice(0, i).join(path.sep)
try {
fs.accessSync(dir)
} catch (err) {
fs.mkdirSync(dir)
}
}
}
makeDirSync('a\\b\\c')
创建目录之异步实现
const fs = require('fs')
const path = require('path')
const {promisify} = require('util')
/* function mkDir (dirPath, cb) {
let parts = dirPath.split('/')
let index = 1
function next () {
if (index > parts.length) return cb && cb()
let current = parts.slice(0, index++).join('/')
fs.access(current, (err) => {
if (err) {
fs.mkdir(current, next)
}else{
next()
}
})
}
next()
}
mkDir('a/b/c', () => {
console.log('创建成功')
}) */
// 将 access 与 mkdir 处理成 async... 风格
const access = promisify(fs.access)
const mkdir = promisify(fs.mkdir)
async function myMkdir (dirPath, cb) {
let parts = dirPath.split('/')
for(let index = 1; index <= parts.length; index++) {
let current = parts.slice(0, index).join('/')
try {
await access(current)
} catch (err) {
await mkdir(current)
}
}
cb && cb()
}
myMkdir('a/b/c', () => {
console.log('创建成功')
})
递归删除目录
const { dir } = require('console')
const fs = require('fs')
const path = require('path')
/**
* 需求:自定义一个函数,接收一个路径,然后执行删除
* 01 判断当前传入的路径是否为一个文件,直接删除当前文件即可
* 02 如果当前传入的是一个目录,我们需要继续读取目录中的内容,然后再执行删除操作
* 03 将删除行为定义成一个函数,然后通过递归的方式进行复用
* 04 将当前的名称拼接成在删除时可使用的路径
*/
function myRmdir (dirPath, cb) {
// 判断当前 dirPath 的类型
fs.stat(dirPath, (err, statObj) => {
if (statObj.isDirectory()) {
// 目录---> 继续读取
fs.readdir(dirPath, (err, files) => {
let dirs = files.map(item => {
return path.join(dirPath, item)
})
let index = 0
function next () {
if (index == dirs.length) return fs.rmdir(dirPath, cb)
let current = dirs[index++]
myRmdir(current, next)
}
next()
})
} else {
// 文件---> 直接删除
fs.unlink(dirPath, cb)
}
})
}
myRmdir('tmp', () => {
console.log('删除成功了')
})
模块化历程
- 传统开发常见问题
- 命名冲突和污染
- 代码冗余,无效请求多
- 文件间的依赖关系复杂
- 模块就是小儿精且利用维护的代码片段
-
模块化规范
模块化是前端走向工程化中的重要的一环
- 早期 JavaScript 语言层面没有模块化规范
- Commonjs、AMD、CMD、都是模块化规范
- ES6 中将模块化纳入标准规范
-
Commonjs 规范
模块引用
- 模块定义
-
module 属性
任意 js 文件就是一个模块,可以直接使用 module 属性
- id: 返回模块标识符,一般是一个绝对路径
- filename: 返回文件模块的绝对路径
- loaded: 返回布尔值,表示模块是否完成加载
- parent:返回对象存放调用当前模块的模块
- children: 返回数组,存放当前模块调用的其他模块
- exports:返回当前模块需要暴露的内容
paths:返回数组,存放不同目录下的 node_modules 位置
modules.exports 与 exports 有何区别?
CommonJS中规定 通过 modules.exports 进行导出操作
- exports 是NodeJS 为了方便操作,给每个文件提供了一个变量,指向了 modules.exports 所对应的内存地址,因此可以通过 exports 导出相应的内容
- exports 导出的时候不能赋值, 因为会切断和 modules.exports对应的联系,成为一个局部变量,无法和外面联系
require 属性
- 基本功能是读入并且执行一个模块文件
- resolve: 返回模块文件绝对路径
- extensions: 依据不同后缀名执行解析操作
- main:返回主模块对象
CommonJS 规范
- CommonJS 规范起初是为了弥补 JS 语言模块化缺陷
- CommonJS 是语言层面的规范,当前主要用于 Node.js
- CommonJS 规定模块化分为引入、定义、标识符三个部分
- Modules 在人意模块中可直接使用包含模块信息
- Require 接收标识符,加载目标模块
- Exports 与 module.exports 都能导出模块数据
-
Nodejs 与 CommonJS
任意一个文件就是一模块,具有独立作用域
- 使用 module.exports与 require 实现模块的导入导出
- module 属性及其常见信息获取
- exports 导出数据及其与 module.exports 区别
- CommonJS 规范下的模块同步加载
// 一、导入
/* let obj = require('./m')
console.log(obj) */
// 二、module
// let obj = require('./m')
// 三、exports
/* let obj = require('./m')
console.log(obj) */
// 四、同步加载
/* let obj = require('./m')
console.log('01.js代码执行了') */
let obj = require('./m')
console.log(require.main == module)
// m.js文件
// 一、模块的导入与导出
/* const age = 18
const addFn = (x, y) => {
return x + y
}
module.exports = {
age: age,
addFn: addFn
} */
// 二、module
/* module.exports = 1111
console.log(module) */
// 三、exports
// exports.name = 'zce'
/* exports = {
name: 'syy',
age: 18
} */
// 四、同步加载
/* let name = 'lg'
let iTime = new Date()
while(new Date() -iTime < 4000) {}
module.exports = name
console.log('m.js被加载导入了') */
/* console.log(require.main == module) */
module.exports = 'lg'
模块加载及加载流程
- 模块分类
- 内置模块
- 文件模块
- 模块加载速度
- 核心模块: Node 源码编译时写入到二进制文件中
- 文件模块: 代码运行时,动态加载
加载流程
- 路径分析: 依据标识符确定模块位置
- 路径标识符
- 非路径标识符: 常见于核心模块
- 文件定位: 确定目标模块中具体的文件及文件类型
- 项目下存在m1.js模块,导入时使用require(‘m1’)语法
- m1.js -> m1.json -> m1.node
- 查找package.json 文件,使用 JSON.parse() 解析
- main.js -> main.json -> main.node
- 将 index 做为目标模块中的具体文件名称
- 编译执行: 采用对应的方式完成文件的编译执行
- 将某个具体类型的文件按照相应的方式及进行编译执行
- 创建新对象,按路径载入,完成编译执行
- JS 文件的编译执行
- 使用 fs 模块同步读入目标文件内容
- 对内容进行语法包装,生成可执行 JS 函数
- 调用函数时传入exports、module、require等属性值
- JSON 文件编译执行
- 将读取到的内容通过 JSON.parse() 进行解析
- 缓存优先原则
- 路径分析: 依据标识符确定模块位置
node.js 是基于事件驱动的一步操作架构,内置 events 模块
- events 模块提供了 EventEmitter 类
- node.js 中很多内置核心模块集成 EventEmitter
EventEmitter 常见API
- on: 添加当事件被触发时调用的回调函数
- emit: 触发事件,按照注册时的序同步调用每个事件监听器
- once:添加当事件在注册之后首次被触发时调用的回调函数
- off: 移除特定的监听器
发布订阅模式
- 发布订阅要素
- 缓存队列,存放订阅者信息
- 具有增加、删除订阅的能力
- 状态改变时通知所有订阅者执行监听
- 发布订阅中存在调度中心
状态发生改变时,发布订阅无须主动通知 ```typescript class PubSub{ constructor() { this._events = {} }
// 注册 subscribe(event, callback) { if (this._events[event]) {
// 如果当前 event 存在,所以我们只需要往后添加当前次监听操作
this._events[event].push(callback)
} else {
// 之前没有订阅过此事件
this._events[event] = [callback]
} }
// 发布 publish(event, …args) { const items = this._events[event] if (items && items.length) {
items.forEach(function (callback) {
callback.call(this, ...args)
})
} } }
let ps = new PubSub() ps.subscribe(‘事件1’, () => { console.log(‘事件1执行了’) }) ps.subscribe(‘事件1’, () => { console.log(‘事件1执行了—-2’) })
ps.publish(‘事件1’) ps.publish(‘事件1’) ```