Node

54 阅读7分钟

Node架构

Natives Modles

  1. 当前层由JS实现
  2. 提供应用程序可直接调用库,例如fs,path,http等
  3. JS语言无法直接操作底层硬件设置,需要bingings来充当桥梁

Bingings

由C或者C++代码实现,它可以帮助我们调用具体的C++函数,但是函数的代码不在这一层

具体功能模块

  1. V8,执行JS代码,将JS代码转化成C
  2. libuv,事件循环,事件队列,异步IO
  3. 其他第三方模块

图示

image.png

Node 特性

轻量级,更适用于IO密集型高并发请求

path

内置模块,require之后直接使用。 用于处理文件/目录的路径。

常见API

  1. basename 获取路径中基础名称
  2. dirname 获取路径中的目录名称
  3. extname 获取路径中的扩展名
  4. isAbsolute 获取路径是否为绝对路径
  5. join 拼接多个路径片段
  6. resolve 返回绝对路径
  7. parse 解析路径
  8. format 序列化路径
  9. normalize 规范化路径
const path = require('path')
console.log(__filename)
// console.log(path.basename(__filename))
// console.log(path.dirname(__filename))
// console.log(path.extname(__filename))
// console.log(path.isAbsolute(__filename))
// console.log(path.join('a/b', 'c', 'index.html'))
// console.log(path.resolve())
// const obj = path.parse('./a/b/c/')
// console.log(path.format(obj))

// console.log(path.normalize('a///b/c../d'))
// 1 获取路径中的基础名称 
/**
 * 01 返回的就是接收路径当中的最后一部分 
 * 02 第二个参数表示扩展名,如果说没有设置则返回完整的文件名称带后缀
 * 03 第二个参数做为后缀时,如果没有在当前路径中被匹配到,那么就会忽略
 * 04 处理目录路径的时候如果说,结尾处有路径分割符,则也会被忽略掉
 */
/* console.log(path.basename(__filename))
console.log(path.basename(__filename, '.js'))
console.log(path.basename(__filename, '.css'))
console.log(path.basename('/a/b/c'))
console.log(path.basename('/a/b/c/')) */

// 2 获取路径目录名 (路径)
/**
 * 01 返回路径中最后一个部分的上一层目录所在路径
 */
/* console.log(path.dirname(__filename))
console.log(path.dirname('/a/b/c'))
console.log(path.dirname('/a/b/c/')) */

// 3 获取路径的扩展名
/**
 * 01 返回 path路径中相应文件的后缀名
 * 02 如果 path 路径当中存在多个点,它匹配的是最后一个点,到结尾的内容
 */
/* console.log(path.extname(__filename))
console.log(path.extname('/a/b'))
console.log(path.extname('/a/b/index.html.js.css'))
console.log(path.extname('/a/b/index.html.js.'))  */

// 4 解析路径
/**
 * 01 接收一个路径,返回一个对象,包含不同的信息
 * 02 root dir base ext name
 */
// const obj = path.parse('/a/b/c/index.html')
// const obj = path.parse('/a/b/c/')
/* const obj = path.parse('./a/b/c/')
console.log(obj.name) */

// 5 序列化路径
/* const obj = path.parse('./a/b/c/')
console.log(path.format(obj)) */

// 6 判断当前路径是否为绝对
/* console.log(path.isAbsolute('foo'))
console.log(path.isAbsolute('/foo'))
console.log(path.isAbsolute('///foo'))
console.log(path.isAbsolute(''))
console.log(path.isAbsolute('.'))
console.log(path.isAbsolute('../bar')) */

// 7 拼接路径
/* console.log(path.join('a/b', 'c', 'index.html'))
console.log(path.join('/a/b', 'c', 'index.html'))
console.log(path.join('/a/b', 'c', '../', 'index.html'))
console.log(path.join('/a/b', 'c', './', 'index.html'))
console.log(path.join('/a/b', 'c', '', 'index.html'))
console.log(path.join('')) */

// 8 规范化路径
/* console.log(path.normalize(''))
console.log(path.normalize('a/b/c/d'))
console.log(path.normalize('a///b/c../d'))
console.log(path.normalize('a//\\/b/c\\/d'))
console.log(path.normalize('a//\b/c\\/d')) */

// 9 绝对路径
// console.log(path.resolve())
/**
 * resolve([from], to)
 */
// console.log(path.resolve('/a', '../b'))
// console.log(path.resolve('index.html'))

buffer

  1. 无须require的一个全局变量,可以直接使用
  2. Buffer让JS可以操作二进制
  3. Node中Buffer是一片内存空间,不占用V8堆内存大小,占用的是C++内存。
  4. 内存的使用由node来控制,由V8的GC回收。
  5. 一般配合Stream流使用,充当数据缓冲区。

image.png

创建Buffer实例

  1. alloc 创建指定字节大小的buffer
  2. allocUnsafe 创建指定大小的buffer 不安全
  3. from 接收数据,创建buffer
/* const b1 = Buffer.alloc(10)
const b2 = Buffer.allocUnsafe(10)

console.log(b1)
console.log(b2) */

// from 
/* const b1 = Buffer.from('中')
console.log(b1) */

// const b1 = Buffer.from([0xe4, 0xb8, 0xad])
/* const b1 = Buffer.from([0x60, 0b1001, 12])
console.log(b1) 
console.log(b1.toString())  */
/* const b1 = Buffer.from('中')
console.log(b1)
console.log(b1.toString()) */

const b1 = Buffer.alloc(3)
const b2 = Buffer.from(b1)

console.log(b1)
console.log(b2)

b1[0] = 1
console.log(b1)
console.log(b2)

buffer 实例方法

  1. fill: 使用数据填充buffer
  2. write 向buffer中写入数据
  3. toString 从buffer中提取数据
  4. slice 截取buffer
  5. indexOf 在buffer中查找数据
  6. 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静态方法

  1. concat 将多个buffer拼接成一个新的buffer
  2. isBuffer 判断当前数据是否是Buffer

/* 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))

fs模块

FS是内置核心模块,提供文件系统操作的API。

权限位:用户对于文件所具备的操作权限。

image.png

标识符:node中flag表示对文件操作方式

  1. r 可读
  2. w 可写
  3. s 同步
  4. +相反操作
  5. x 排他操作
  6. a 追加操作

操作符:fd就是操作系统分配给被打开文件的标识

文件的读写与拷贝操作

  1. readFile 从指定文件读取数据
  2. write 向指定文件写入数据
  3. appendFile 追加的方式向指定文件写入数据
  4. copyFile 将某个文件的数据拷贝至另一文件
  5. watchFile 对指定文件进行监控
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) => {
  if (curr.mtime !== prev.mtime) {
    console.log('文件被修改了')
    fs.unwatchFile('data.txt')
  }
})

文件的打开与关闭

  1. open
  2. close
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('关闭成功')
  })
})

大文件的读写操作

针对大文件的读写,一般放到buffer中逐步读写操作,而不是在内存中一次性读写。

image.png

常见目录操作API

  1. access 判断文件或目录是否具有操作权限
  2. stat 获取目录及文件信息
  3. mkdir 创建目录
  4. rmdir 删除目录
  5. readdir 读取目录中内容
  6. unlink 删除指定文件
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('删除成功')
  }
}) */

模块化规范

  1. common.js require 同步导入 一般用在node环境中,浏览器环境不支持
  2. amd
  3. cmd
  4. esm import 浏览器常用的导入,异步加载,官方规范

vm

创建独立运行的沙箱环境

Node中的事件环

队列说明

  1. timers 执行setTimeout 与 setInterval 回调
  2. pending callbacks 执行操作系统的回调 例如tcp udp
  3. idle prepare 只在系统内部使用
  4. poll 执行与i/o相关的回调 文件、网络等
  5. check 执行setimmediate 中的回调
  6. close callbacks 执行close事件的回调 close 事件(如 socket.on('close')

6个队列阶段会依次执行,但是每个阶段结束后,会先清空微任务队列,在进入下一阶段。

procrss.nexttick 优先级高于promise.then

如果微任务中又触发了微任务,会一直执行到微任务队列清空,可能阻塞事件循环。

如果宏任务中嵌套了宏任务,会放到下一次事件循环中

如果poll阶段过快,或者读取的是缓存,poll阶段回调函数来不及注册,那么就会优先执行check阶段,等到下一次循环在执行poll阶段的回调

const fs = require('fs');

setTimeout(() => {
  console.log('1️⃣ setTimeout');

  Promise.resolve().then(() => {
    console.log('1️⃣🔁 微任务 after setTimeout');
  });
}, 0);

setImmediate(() => {
  console.log('2️⃣ setImmediate');

  Promise.resolve().then(() => {
    console.log('2️⃣🔁 微任务 after setImmediate');
  });
});

fs.readFile(__filename, () => {
  console.log('3️⃣ fs.readFile (I/O 完成)');

  Promise.resolve().then(() => {
    console.log('3️⃣🔁 微任务 after fs.readFile');
  });
});

Promise.resolve().then(() => {
  console.log('0️⃣🔁 初始微任务');
});

process.nextTick(() => {
  console.log('0️⃣🌀 process.nextTick');
});

  1. 0️⃣🌀 process.nextTick
  2. 0️⃣🔁 初始微任务
  3. 1️⃣ setTimeout
  4. 1️⃣🔁 微任务 after setTimeout
  5. 2️⃣ setImmediate
  6. 2️⃣🔁 微任务 after setImmediate
  7. 3️⃣ fs.readFile (I/O 完成)
  8. 3️⃣🔁 微任务 after fs.readFile

  1. 可写流:
  2. 可读流
  3. 管道流(可读可写)
  4. transform流 边读边压缩

背压机制

当写入速度 < 读取速度 时,写入流会返回 false。你需要监听 'drain' 事件,避免内存溢出。

const fs = require('fs');

const readable = fs.createReadStream('bigfile.txt');
const writable = fs.createWriteStream('output.txt');

readable.on('data', (chunk) => {
  const canWrite = writable.write(chunk);
  if (!canWrite) {
    console.log('⚠️ 写入缓冲区满了,暂停读取...');
    readable.pause(); // 暂停读取
  }
});

writable.on('drain', () => {
  console.log('✅ drain: 写入缓冲区已清空,恢复读取');
  readable.resume(); // 恢复读取
});