Node.js核心模块

Node.js核心模块

内置模块之 PATH(用于处理文件/目录的路径)

path模块提供了一些实用工具,用于处理文件和目录的路径

  • basename() 获取路径中基础名称
  1. 返回的就是接收路径当中的最后一部分
  2. 第二个参数表示扩展名,如果说没有设置则返回完整的文件名称带后缀
  3. 第二个参数作为后缀时,如果没有在当前路径中被匹配到,那么就被会忽略
  4. 处理目录路径的时候如果说,结尾处有路径分隔符,则也会被忽略掉
  • dirname() 获取路径中目录名称

  • extname() 获取路径中扩展名称

  • isAbsolute() 获取路径是否为绝对路径

  • join() 拼接多个路径片段

  • resolve() 返回绝对路径

  • parse() 解析路径

  • format() 序列化路径

  • normalize() 规范化路径

Buffer 对象用于表示固定长度的字节序列。

+ 无须require的一个全局变量
+ 实现nodejs 平台下的二进制数据操作
+ 不占据V8堆内存大小的内存空间

Fs 文件操作系统

+ fs是Nodejs中内置核心模块
+ 代码层面上fs分为基本操作类和常用api
+ 权限位、标识符、操作符
  • 常见 flag 操作符
    • r:表示可读
    • w:表示可写
    • s:表示同步
    • +:表示执行相反操作
    • x:表示排它操作
    • a:表示追加操作

文件的打开与操作

  • fs.open(path, flags, [mode], [callback(err,fd)])

    • path 文件路径
    • flags 可以是以下的值
    • ‘r’ - 以读取模式打开文件
    • ‘r+’ - 以读写模式打开文件
    • ‘rs’ - 使用同步模式打开并读取文件。指示操作系统忽略本地文件系统缓存
    • ‘rs+’ - 以同步的方式打开,读取 并 写入文件
    • 注意:这不是让fs.open变成同步模式的阻塞操作。如果想要同步模式请使用fs.openSync()。
    • ‘w’ - 以读取模式打开文件,如果文件不存在则创建
      ‘wx’ - 和 ’ w ’ 模式一样,如果文件存在则返回失败
      ‘w+’ - 以读写模式打开文件,如果文件不存在则创建
      ‘wx+’ - 和 ’ w+ ’ 模式一样,如果文件存在则返回失败
    • ‘a’ - 以追加模式打开文件,如果文件不存在则创建
      ‘ax’ - 和 ’ a ’ 模式一样,如果文件存在则返回失败
      ‘a+’ - 以读取追加模式打开文件,如果文件不存在则创建
      ‘ax+’ - 和 ’ a+ ’ 模式一样,如果文件存在则返回失败
  • mode 用于创建文件时给文件制定权限,默认0666

  • callback 回调函数会传递一个文件描述符 fd ,和一个异常err

大文件读写操作

A文件要把内容复制到B文件里, 需要一个内存来暂存内容,边读边写,可以用Buffer来实现,避免直接复制过去造成内存的溢出。

const fs = require('fs')
 
// read : 所谓的读操作就是将数据从磁盘文件中写入到 buffer 中
let buff = Buffer.alloc(10)
 
/**
 * fd 定位当前被打开的文件
 * rfd 文件标识符
 * buf 用于表示当前缓冲区
 * offset 表示当前从 buf 的哪个位置开始执行写入
 * length 表示当前写入的长度
 * position 表示当前从文件的那个位置开始读取
 */
 
fs.open('data.txt', 'r', (err, rfd) => {
    console.log(rfd); // 3
    fs.read(rfd, buff, 0, 4, 3, (err, readBytes, data) => {
        console.log(readBytes); // 4
        console.log(data); // <Buffer 34 35 36 37 00 00 00 00 00 00>
        console.log(data.toString()); // 4567
    })
})
 
 
buf = Buffer.from('1234567890')
fs.open('b1.txt', 'w', (err, wfd) => {
    /**
     * wfd 文件标识符
     * buf 存放数据的缓存区
     * 1 第一个位置是从 buffer 的哪个位置取数据
     * 4 表示具体写多少个长度
     * 0 表示从文件的哪个位置执行写操作,一版从0开始执行写操作
    */  
    fs.write(wfd, buf, 1, 4, 0, (err, written, buffer) => {
        console.log(written, '----'); // 4 ----
        fs.close(wfd) // 记得关闭文件,减少内存的占用
    })
})
 
// 会生成一个 b1.txt 文件,内容是 2345

文件拷贝自定义实现

const fs = require('fs')
 
/**
 * 01 打开 a 文件,利用 read将数据保存在 buffer 暂存起来
 * 02 打开 b 文件,利用 write 将 buffer 中数据写入到 b 文件中
*/
 
let buf = Buffer.alloc(10)
 
// 01 打开制定的文件 拷贝10个字节
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, writter) =>{
                console.log('写入成功');
            })
        })
    })
})
 
// 02 数据的完全拷贝
// fs.open('a.txt', 'r', (err, rfd) => {
//     fs.open('b.txt', 'w', (err, wfd) => {
//         fs.read(rfd, buf, 0, 10, 0, (err, readBytes) => {
//             fs.write(wfd, buf, 0, 10, 0, (err, writter) =>{
//                 fs.read(rfd, buf, 0, 5, 10, (err, readBytes) => {
//                     fs.write(wfd, buf, 0, 5, 10, (err,writter) => {
//                         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, writter) => {
                    next()
                })
            })
        }
        next()
    })
})
 
// 生成一个 b.txt 文件,内容为A的内容

FS 之目录操作 API

常见目录操作 API

  • access:判断文件或目录是否具有操作权限

  • stat:获取目录及文件信息

  • mkdir:创建目录

  • rmdir:删除目录

  • readdir:读取目录中内容

  • unlink:删除指定文件

创建目录之同步实现

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')
 
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('创建成功');
})

目录删除之异步实现

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('删除成功了');
})

CommonJs 规范

+ 模块引用
+ 模块定义
+ 模块标识

Nodejs与CommonJs

+ 任意一个文件就是一模块,具有独立作用域
+ 使用require导入其它模块
+ 将模块ID传入require实现目标模块定位

模块分类及加载流程

  • 模块分类

    • 内置模块

    • 文件模块

  • 模块加载速度

    • 核心模块:Node 源码编译时写入到二进制和文件中

    • 文件模块:代码运行时,动态加载

  • 加载流程

    • 路径分析:依据标识符确定模块位置

    • 文件定位:确定目标模块中具体的文件及文件类型

    • 编译执行:采用对应的方式完成文件的编译执行

  • 路径分析之标识符

    • 路径标识符

    • 非路径标识符(非路径常见于核心模块)

  • 文件定位

  • 编译执行

    • 将某个具体类型的文件按照相应的方式进行编译和执行

    • 创建新对象,按路径载入,完成编译执行

  • JS文件的编译执行

    • 使用fs模块同步读入目标文件内容
    • 对内容进行语法包装,生成可执行JS函数
    • 调用函数时传入exports、module、require等属性值
  • JSON文件编译执行

    • 将读取到的内容通过JSON.parse()进行解析
  • 缓存优化原则

    • 提高模块加载速度
    • 当前模块不存在,则经历一次完整加载流程
    • 模块加载完成后,使用路径作为索引进行缓存
  • 加载流程小结

    • 路径分析:确定目标模块位置

    • 文件定位:确定目标模块中的具体文件

    • 编译执行:对模块内容进行编译,返回可用 exports 对象

核心模块之 Events

  • 通过 EventEmitter 类实现事件统一管理

  • events 与 EventEmitter

    • node.js 是基于时间驱动的异步操作架构,内置 events 模块

    • events 模块提供了 EventEmitter 类

    • node.js 中很多内置核心模块继承 EventEmitter

  • EventEmitter 常见 API

    • on:添加当事件被触发时调用的回调函数

    • emit:触发事件,按照注册的序同步调用每个事件监听器

    • once:添加当事件在注册之后首次被触发时调用的回调函数

    • off:移除特定的监听器

发布订阅

  • 定义对象间一对多的关系

  • 发布订阅要素

    • 缓存队列,存放订阅者信息

    • 具有增加、删除订阅的能力

    • 状态改变时通知所有订阅者执行监听

  • 发布订阅中存在调度中心

  • 状态发生改变时,发布订阅者无须主动通知

  • EventEmitter模拟

function MyEvent () {
    // 准备一个数据结构用于缓存订阅者信息
    this._events = Object.create(null)
}
 
MyEvent.prototype.on = function (type, callback) {
    // 判断当前次的事件是否已经存在,然后再决定如果做缓存
    if (this._events[type]) {
        this._events[type].push(callback)
    } else {
        this._events[type] = [callback]
    }
}
 
MyEvent.prototype.emit = function (type, ...args) {
    if (this._events && this._events[type].length) {
      this._events[type].forEach((callback) => {
        callback.call(this, ...args)
      })
    }
  }
 
MyEvent.prototype.off = function (type, callback) {
    // 判断当前 type 事件监听是否存在,如果存在则取消指定的监听
    if (this._events && this._events[type]) {
        this._events[type] = this._events[type].filter((item) => {
            return item !== callback && item.link !== callback
        })
    }
}
 
 
MyEvent.prototype.once = function (type, callback) {
    let foo = function (...args) {
        callback.call(this, ...args)
        this.off(type, foo)
    }
    foo.link = callback
    this.on(type, foo)
}
 
let ev = new MyEvent()
 
let fn = function (...data) {
    console.log('事件1执行了', data);
}
 
// ev.on('事件1', fn)
// ev.on('事件1',  () => {
//     console.log('事件1---2');
// })
 
// ev.emit('事件1', 1, 2)
// ev.emit('事件1', 1, 2)
 
// 事件1执行了 [ 1, 2 ]
// 事件1---2
// 事件1执行了 [ 1, 2 ]
 
// ev.on('事件1', fn)
// ev.emit('事件1', '前')
// ev.off('事件1', fn)
// ev.emit('事件1', '后')
// 事件1执行了 [ '前' ]
 
ev.once('事件1', fn)
ev.off('事件1', fn)
// ev.emit('事件1', '前')
ev.emit('事件1', '后')
 
// 1
// 事件1执行了 [ '前' ]

浏览器中的 Eventloop 事件环

完整事件环执行顺序

  • 从上至下执行所有的同步代码
  • 执行过程中将遇到的宏任务与微任务添加至相应的队列
  • 同步代码执行完毕后,执行满足条件的微任务回调
  • 微任务队列执行完毕后执行所有满足需求的宏任务回调
  • 循环事件环操作

*注意:每执行一个宏任务之后就会立刻检查微任务对列*

Nodejs 下的事件环

  • 队列说明:从上至下执行

    • timers:执行 setTimeout 与 setInterval 回调

    • pengding callbacks:执行系统操作的回调,例如tcp udp

    • idle,prepare:只在系统内部进行使用

    • poll:执行与 I/O 相关的回调

    • check:执行 setImmediate 中的回调

    • close callbacks:执行 close 事件的回调

  • Nodejs 完整事件环

    • 执行同步代码,将不同的任务添加至相应的队列

    • 所有同步代码执行后回去执行满足条件微任务

    • timer 中的所有宏任务执行完成后就会依次切换队列

Nodejs与浏览器事件环区别

  • 任务队列数不同

    • 浏览器中只有二个任务队列

    • Nodejs 中有6个事件队列

  • Nodejs 微任务执行实际不同

    • 二者都会在同步代码执行完毕后执行微任务

    • 浏览器平台下每当一个宏任务执行完毕后就清空微任务

    • Nodejs 平台在事件队列切换时会去清空微任务

  • 微任务优先级不同

    • 浏览器事件环中,微任务存放于事件队列,先进先出

    • Nodejs 中 process.nextTick 先于 promise.then

Nodejs事件环常见问题

  • setTimeout 和 setImmediate同步执行顺序是随机的

原因:是因为 setTimeout后面要跟一个时间,没写的情况下默认是0,不管是在Node和浏览器的平台下,都会有一个不稳定的因素,有些情况下会产生一些延时,延时的话,代码从上往下,会先加载 setImmediate,后加载 setTimeout;没有延时,就是先加载 setTimeout,后加载 setImmediate

解决办法:放在 I/O 回调函数,就不会出现这样的情况

核心模块之stream

  • ls|grep *.js将左边的数据写到右边 js 文件中

  • Node.js 诞生之初就是为了提高 IO 性能

  • 文件操作系统和网络模块实现了流接口

  • Node.js 中的流就是处理流式数据的抽象接口

  • 应用程序中为什么使用流来处理数据?

    • 原来方式常见问题:

      • 同步读取资源文件,用户需要等待数据读取完成

      • 资源文件最终一次性加载至内存,开销较大

    • 现在方式

      • 流处理数据的优势

        • 时间效率:流的分段处理可以同时操作多个数据 chunk

        • 空间效率:同一时间流无须占据大内存空间

        • 使用方便:流配合管理,扩展程序变得简单,

  • Node.js 内置了 stream,它实现了流操作对象

  • Node.js 中流的分类

    • Readable:可读流,能够实现数据的读取

    • Writeable:可写流,能够实现数据的写操作

    • Duplex:双工流,即可读又可写

    • Tranform:转换流,可读可写,还能实现数据转换

  • Node.js 流特点

    • Stream 模块实现了四个具体的抽象

    • 所有流都继承自 EventEmitter

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值