Appearance
Node.js
常见的内置模块
path
path 模块用于对路径和文件进行处理,提供了很多好用的方法
从路径中获取信息:
- dirname:获取文件的父文件夹;
- basename:获取文件名;
- extname:获取文件扩展名。
路径拼接:
- path.join()
将文件和某个文件夹拼接:
- path.resolve():resolve函数会判断我们拼接的路径前面是否有
/
或../
或./
,如果有表示是一个绝对路径,会返回对应的拼接路径, 如果没有,那么会和当前执行文件所在的文件夹进行路径的拼接。
fs
fs 是 File System 的缩写,表示文件系统。
API 大多数都提供三种操作方式
- 方式一:同步操作文件:代码会被阻塞,不会继续执行;
- 方式二:异步回调函数操作文件:代码不会被阻塞,需要传入回调函数,当获取到结果时,回调函数被执行;
- 方式三:异步 Promise 操作文件:代码不会被阻塞,通过 fs.promises 调用方法操作,会返回一个 Promise, 可以通过 then、catch 进行处理;
fs 模块中的一些方法:
- fs.open() 方法用于分配新的文件描述符;
- fs.readFile(path[, options], callback):读取文件的内容;
- fs.writeFile(file, data[, options], callback):在文件中写入内容;
- fs.mkdir() 或 fs.mkdirSync():创建一个新文件夹;
- fs.rename():文件重命名;
events
发出事件和监听事件都是通过EventEmitter类来完成的,它们都属 于events对象
- emitter.on(eventName, listener):监听事件,也可以使用 addListener;
- emitter.off(eventName, listener):移除事件监听,也可以使 用removeListener;
- emitter.emit(eventName[, ...args]):发出事件,可以携带一 些参数;
常见的属性:
- emitter.eventNames():返回当前 EventEmitter对象注册的事件字符串数组;
- emitter.getMaxListeners():返回当前 EventEmitter对象的最大监听器数量,可以通过setMaxListeners() 来修改,默认是10;
- emitter.listenerCount(事件名称):返回当前 EventEmitter对象某一个事件名称,监听器的个数;
- emitter.listeners(事件名称):返回当前 EventEmitter对象某个事件监听器上所有的监听器数组
常见的包管理工具
npm
packag.json 中必填的属性:name、version。
常见的属性:
- name 是项目的名称;
- version 是当前项目的版本号;
- description 是描述信息,很多时候是作为项目的基本描述;
- author 是作者相关信息(发布时用到);
- license 是开源协议(发布时用到);
- main 设置程序的入口;
- script 用于配置一些脚本命令,以键值对的形式存在;
- dependencies 是指定无论开发环境还是生成环境都需要依赖的包;
- devDependencies 一些包在生产环境是不需要的,比如 webpack、babel 等,我们会通过
npm install webpack --save-dev
,将它安装到 devDependencies 属性中;
疑问:那么在生产环境如何保证不安装这些包呢?
生产环境不需要安装时,我们需要通过 npm install --production 来安装文件的依赖
- engines 用于指定Node和NPM的版本号;
- browserslist 用于配置打包后的JavaScript浏览器的兼容情况;
npm install 原理图:
npm 其他命令
- npm rebuild:强制重新打包。
- npm cache clean:清除缓存。
yarn
yarn 是为了弥补 npm 的一些缺陷而出现的。
早期的 npm 存在很多的缺陷,比如安装依赖速度很慢、版本依赖混乱等等一系列的问题
pnpm
如何开发脚手架工具
使用 Commander 库
Buffer
Buffer 和二进制
计算机中所有的内容:文字、数字、图片、音频、视频最终都会使用二进制来表示。
我们可以将 Buffer 看成是一个存储二进制的数组,这个数组中的每一项,可以保存8位二进制: 00000000。
为什么是8位呢?
因为一位二进制存储的数据是非常有限的,以通常会将8位合在一起作为一个单元,这个单元称之为一个字节(byte)。
Buffer 创建
- Buffer.from
- Buffer.alloc
Buffer 和文件读取
- 文本文件的读取:
js
fs.readFile('./test/text', (err, data) => {
console.log(data) // <Buffer 48 65 6c 6c 6f 20 57 6f 72 6c 64>
console.log(data.toString()) // Hello World
})
- 图片文件的读取:
js
fs.readFile('./img.jpg', (err, data) => {
console.log(data) // <Buffer ... more bytes>
})
js
sharp('./test.png').resize(1000, 1000).toBuffer().then(data => {
fs.writeFileSync('./test.copy.png', data)
})
Buffer的创建过程
我们创建Buffer时,并不会频繁的向操作系统申请内存,它会默认先申请一个8 * 1024个字节大小的内存, 也就是8kb
Stream
文件的读写时,我们可以直接通过 readFile或者 writeFile方式读写文件,为什么还需要流呢?
- 直接读写文件的方式,虽然简单,但是无法控制一些细节的操作;
- 比如从什么位置开始读、读到什么位置、一次性读取多少个字节;
- 读到某个位置后,暂停读取,某个时刻恢复读取等等;
- 或者这个文件非常大,比如一个视频文件,一次性全部读取并不合适。
文件读写的 Stream
事实上Node中很多对象是基于流实现的:
- http模块的Request和Response对象
- process.stdout对象
官方:另外所有的流都是EventEmitter的实例
Node.js中有四种基本流类型:
- Writable:可以向其写入数据的流(例如 fs.createWriteStream())。
- Readable:可以从中读取数据的流(例如 fs.createReadStream())。
- Duplex:同时为Readable和的流Writable(例如 net.Socket)。
- Transform:Duplex可以在写入和读取数据时修改或转换数据的流(例如zlib.createDeflate())
Readable
使用 fs.readFile
读取一个文件信息,这种方式是一次性将一个文件中所有的内容都读取到程序(内存)中,但是这种读取方式会出现文件过大、读取的位置、结束的位置、一次读取的大小等问题。
我们可以使用 createReadStream 来读取文件,常用参数:
- start:文件读取开始的位置;
- end:文件读取结束的位置;
- highWaterMark:一次性读取字节的长度,默认是64kb。
Writable
使用 createWriteStream 来写入文件,常用参数:
- flags:默认是w,如果我们希望是追加写入,可以使用 a或者 a+;
- start:写入的位置
pipe 方法
正常情况下,我们可以将读取到的 输入流,手动的放到 输出流中进行写入
js
const reader = fs.createReadStream('./foo.txt')
const writer = fs.createWriteStream('./bar.txt')
reader.on("data", (data) => {
console.log(data)
writer.write(data, (err) => {
console.log(err)
})
})
我们也可以通过pipe来完成这样的操作
js
reader.pipe(writer)
Http 模块
创建一个简单的 web 服务器
js
const http = require('http')
const HTTP_PORT = 8000
const server = http.createServer((req, res) => {
res.end("Hello World")
})
server.listen(HTTP_PORT, () => {
console.log(`服务器在${HTTP_PORT}启动~`)
})
request 对象
在向服务器发送请求时,我们会携带很多信息:
- URL
- 请求方式 method
- 请求头 headers
文件上传
js
// 图片文件必须设置位二进制的
req.serEncoding('binary')
// 获取 content-type 中的 boundary 的值
var boundary = req.headers['content-type'].split('; ')[1].replace('boundary=', '')
// 记录当前数据的信息
const fileSize = req.headers['content-length']
let curSize = 0
let body = ''
// 监听当前的数据
req.on("data", (data) => {
curSize += data.length
res.write(`文件上传进度:${ curSize/fileSize * 100 }%\n`)
body += data
})
req.on('end', () => {
// 切割数据
const payload = qs.parse(body, "\r\n", ":")
// 获取最后的类型(image/png)
const fileType = payload["Content-type"].substring(1)
// 获取要截取的长度
//·获取要截取的长度
const fileTypePosition = body.indexof(fileType) + fileType.length
let binaryData = body.substring(fileTypePosition)
binaryData = binaryData.replace(/^\s\s*/, '')
// binaryData = binaryData.replaceAll('\r\n', '')
const finalData = binaryData.substring(0, binaryData. indexOf('--' + boundary + '--'))
fs.writeFile('./boo.png', finalData, 'binary', (err) => {
console.log(err)
res.end("文件上传完成~")
})
})