Node 核心编程 -- 核心模块二

12 篇文章 1 订阅

核心模块之 Events

EventEmitter 常见 API

  • on:添加当事件被触发时调用的回调函数
  • emit:触发事件,按照注册的顺序同步调用每个事件监听器
  • once:添加当事件在注册之后首次被触发时调用的回调函数
  • off:移除特定的监听器

发布订阅模式
在这里插入图片描述

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')

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

/* ev.on('事件1', fn)
ev.emit('事件1', '前')
ev.off('事件1', fn)
ev.emit('事件1', '后') */

ev.once('事件1', fn)
// ev.off('事件1', fn)
ev.emit('事件1', '前')

浏览器中的 Eventloop

完整事件循环执行顺序

  • 从上至下执行所有的同步代码
  • 执行中遇到的宏任务与微任务加至相应的队列
  • 同步代码执行完毕后,执行满足条件的微任务回调
  • 微任务队列执行完毕后执行所有满足需求的宏任务回调
  • 循环事件环操作
  • 注意:每执行一个宏任务之后就会立刻检查微任务队列

Nodejs 下的事件循环

Nodejs 事件循环机制
在这里插入图片描述
队列说明

  • timers:执行 setTimout 与 setInterval 回调
  • pending callback:执行系统操作的回调,例如 tcp udp
  • idle,prepare:只在系统内部进行使用
  • poll:执行与 I/O相关的回调
  • check:执行 setImmediate 中的回调
  • close callbacks:执行 close 事件的回调

Nodejs 完整事件循环

  • 执行同步代码,将不同的任务添加至相应的队列
  • 所有同步代码执行后会去执行满足条件微任务
  • 所有微任务代码执行后会执行 timer 队列中满足的宏任务
  • timer 中的所有宏任务执行完成后就会依次切换队列
  • 注意:在完成队列切换之前会先清空微任务代码

Nodejs 与浏览器事件循环区别

  • 任务队列数不同

    • 浏览器中只有2个任务队列
    • Nodejs 中有6个事件队列
  • Nodejs 微任务执行时机不同

    • 二者都会在同步代码执行完成后执行微任务
    • 浏览器平台下每当一个宏任务执行完毕后就会清空微任务
    • Nodejs 平台在事件队列切换时会去清空微任务
  • 微任务优先级不同

    • 浏览器环境中,微任务存放于事件队列,先进先出
    • Nodejs 中 process.nextTick 先于 promise.then

核心模块之 Stream

流处理数据的优势

  • 时间效率:流的分段处理可以同时操作多个数据 chunk
  • 空间效率:同一时间流无须占据大内存空间
  • 使用方便:流配合管理,扩展程序变的简单

Node.js 中流的分类

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

    • 自定义可读流
    const {Readable} = require('stream')
    
    // 模拟底层数据
    let source = ['lg', 'zce', 'syy']
    
    // 自定义类继承 Readable
    class MyReadable extends Readable{
      constructor(source) {
        super()
        this.source = source
      }
      _read() {
        let data = this.source.shift() || null 
        this.push(data)
      }
    }
    
    // 实例化
    let myReadable = new MyReadable(source)
    
    /* myReadable.on('readable', () => {
      let data = null 
      while((data = myReadable.read(2)) != null) {
        console.log(data.toString())
      }
    }) */
    
    myReadable.on('data', (chunk) => {
      console.log(chunk.toString())
    })
    
  • Writable:可写流,能够实现数据的写操作

    • 自定义可写流
    const {Writable} = require('stream')
    
    class MyWriteable extends Writable{
      constructor() {
        super()
      }
      _write(chunk, en, done) {
        process.stdout.write(chunk.toString() + '<----')
        process.nextTick(done)
      }
    }
    
    let myWriteable = new MyWriteable()
    
    myWriteable.write('拉勾教育', 'utf-8', () => {
      console.log('end')
    })
    
    • 可写流事件
      • pipe 事件:可读流调用 pipe() 方法时触发
      • unpipe 事件:可读流调用 unpipe() 方法时触发
      • drain 事件:write 返回 false ,数据执行可写入时触发
  • Duplex:双工流,既可读又可写

    • 自定义双工流
    let {Duplex} = require('stream')
    
    class MyDuplex extends Duplex{
      constructor(source) {
        super()
        this.source = source
      }
      _read() {
        let data = this.source.shift() || null 
        this.push(data)
      }
      _write(chunk, en, next) {
        process.stdout.write(chunk)
        process.nextTick(next)
      }
    }
    
    let source = ['a', 'b', 'c']
    let myDuplex = new MyDuplex(source)
    
    /* myDuplex.on('data', (chunk) => {
      console.log(chunk.toString())
    }) */
    myDuplex.write('拉勾教育', () => {
      console.log(1111)
    })
    
  • Transform:转换流,可读可写,还能实现数据转换

    • 自定义转换流
    let {Transform} = require('stream')
    
    class MyTransform extends Transform{
      constructor() {
        super()
      }
      _transform(chunk, en, cb) {
        this.push(chunk.toString().toUpperCase())
        cb(null)
      }
    }
    
    let t = new MyTransform()
    
    t.write('a')
    
    t.on('data', (chunk) => {
      console.log(chunk.toString())
    })
    

Node.js 流特点

  • Stream 模块实现了具体的四个抽象
  • 所有流都继承自 EventEmitter

背压机制

let fs = require('fs')

let rs = fs.createReadStream('test.txt', {
  highWaterMark: 4
})

let ws = fs.createWriteStream('test1.txt', {
  highWaterMark: 1
})

let flag = true

/* rs.on('data', (chunk) => {
  flag = ws.write(chunk, () => {
    console.log('写完了')
  })
  if (!flag) {
    rs.pause()
  }
})

ws.on('drain', () => {
  rs.resume()
}) */

rs.pipe(ws)

模拟文件可读流

const fs = require('fs')
const EventEmitter = require('events')

class MyFileReadStream extends EventEmitter{
  constructor(path, options = {}) {
    super()
    this.path = path
    this.flags = options.flags || "r"
    this.mode = options.mode || 438
    this.autoClose = options.autoClose || true 
    this.start = options.start || 0
    this.end = options.end 
    this.highWaterMark = options.highWaterMark || 64 * 1024 
    this.readOffset = 0

    this.open()

    this.on('newListener', (type) => {
      if (type === 'data') {
        this.read()
      }
    })
  }
  open() {
    // 原生 open 方法来打开指定位置上的文件
    fs.open(this.path, this.flags, this.mode, (err, fd) => {
      if (err) {
        this.emit('error', err)
      }
      this.fd = fd
      this.emit('open', fd)
    })
  }
  read() {
    if (typeof this.fd !== 'number') {
      return this.once('open', this.read)
    }

    let buf = Buffer.alloc(this.highWaterMark)

    let howMuchToRead
    /* if (this.end) {
      howMuchToRead = Math.min(this.end - this.readOffset + 1, this.highWaterMark)
    } else {
      howMuchToRead = this.highWaterMark
    } */

    howMuchToRead = this.end ? Math.min(this.end - this.readOffset + 1, this.highWaterMark) : this.highWaterMark

    fs.read(this.fd, buf, 0, howMuchToRead, this.readOffset, (err, readBytes) => {
      if (readBytes) {
        this.readOffset += readBytes
        this.emit('data', buf.slice(0, readBytes))
        this.read()
      } else {
        this.emit('end')
        this.close()
      }
    })
  }
  close() {
    fs.close(this.fd, () => {
      this.emit('close')
    })
  }
  pipe(ws){
    this.on('data', (data) => {
      let flag = ws.write(data)
      if(!flag){
        this.pause()
      }
    })
    ws.on('drain', () => {
      this.resume()
    })
  }
}

let rs = new MyFileReadStream('test.txt', {
  end: 7,
  highWaterMark: 3
})

rs.on('data', (chunk) => {
  console.log(chunk)
})

链表结构

为什么不采取数组存储数据

  • 数组存储数据的长度具有上限
  • 数组存在塌陷问题

在这里插入图片描述
单向链表实现

/* 
  01 node + head + null 
  02 head --->null 
  03 size 

  04 next element

  05 增加 删除 修改 查询 清空 
*/

class Node{
  constructor(element, next) {
    this.element = element
    this.next = next
  }
}

class LinkedList{
  constructor(head, size) {
    this.head = null 
    this.size = 0
  }
  _getNode(index) {
    if (index < 0 || index >= this.size) {
      throw new Error('越界了')
    }
    let currentNode = this.head
    for (let i = 0; i < index; i++) {
      currentNode = currentNode.next
    }
    return currentNode
  }
  add(index, element) {
    if (arguments.length == 1) {
      element = index
      index = this.size
    }
    if (index < 0 || index > this.size) {
      throw new Error('cross the border')
    }
    if (index == 0) {
      let head = this.head // 保存原有 head 的指向
      this.head = new Node(element, head)
    } else {
      let prevNode = this._getNode(index - 1)
      prevNode.next = new Node(element, prevNode.next)
    }
    this.size++
  }

  remove(index) {
    let rmNode = null 
    if (index == 0) {
      rmNode = this.head 
      if (!rmNode) {
        return undefined
      }
      this.head = rmNode.next
    } else {
      let prevNode = this._getNode(index -1)
      rmNode = prevNode.next
      prevNode.next = rmNode.next
    }
    this.size--
    return rmNode
  }
  set(index, element) {
    let node = this._getNode(index)
    node.element = element
  }
  get(index) {
    return this._getNode(index)
  }
  clear() {
    this.head = null 
    this.size = 0 
  }
}

单向链表实现队列

class Queue{
  constructor() {
    this.linkedList = new LinkedList()
  }
  enQueue(data) {
    this.linkedList.add(data)
  }
  deQueue() {
    return this.linkedList.remove(0)
  }
}

const q = new Queue()

q.enQueue('node1')
q.enQueue('node2')

let a = q.deQueue()
a = q.deQueue()
a = q.deQueue()

console.log(a)

文件可写流实现

const fs = require('fs')
const EventsEmitter = require('events')
const Queue = require('./linkedlist')

class MyWriteStream extends EventsEmitter{
  constructor(path, options={}) {
    super()
    this.path = path
    this.flags = options.flags || 'w'
    this.mode = options.mode || 438
    this.autoClose = options.autoClose || true 
    this.start = options.start || 0
    this.encoding = options.encoding || 'utf8'
    this.highWaterMark = options.highWaterMark || 16*1024

    this.open()

    this.writeoffset = this.start 
    this.writing = false 
    this.writeLen = 0
    this.needDrain = false 
    this.cache = new Queue()
  }
  open() {
    // 原生 fs.open 
    fs.open(this.path, this.flags, (err, fd) => {
      if (err) {
        this.emit('error', err)
      }
      // 正常打开文件
      this.fd = fd 
      this.emit('open', fd)
    })
  }
  write(chunk, encoding, cb) {
    chunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)

    this.writeLen += chunk.length
    let flag = this.writeLen < this.highWaterMark
    this.needDrain = !flag

    if (this.writing) {
      // 当前正在执行写入,所以内容应该排队
      this.cache.enQueue({chunk, encoding, cb})
    } else {
      this.writing = true
      // 当前不是正在写入那么就执行写入
      this._write(chunk, encoding, () => {
        cb()
        // 清空排队的内容
        this._clearBuffer()
      })
    }
    return flag
  }
  _write(chunk, encoding, cb) {
    if (typeof this.fd !== 'number') {
      return this.once('open', ()=>{return this._write(chunk, encoding, cb)})
    }
    fs.write(this.fd, chunk, this.start, chunk.length, this.writeoffset, (err, written) => {
      this.writeoffset += written
      this.writeLen -= written

      cb && cb()
    })
  }
  _clearBuffer() {
    let data = this.cache.deQueue()
    if (data) {
      this._write(data.element.chunk, data.element.encoding, ()=>{
        data.element.cb && data.element.cb()
        this._clearBuffer()
      })
    } else {
      if (this.needDrain) {
        this.needDrain = false 
        this.emit('drain')
      }
    }
  }
}

const ws = new MyWriteStream('./f9.txt', {})

ws.on('open', (fd) => {
  console.log('open---->', fd)
})

let flag = ws.write('1', 'utf8', () => {
  console.log('ok1')
})

flag = ws.write('10', 'utf8', () => {
  console.log('ok1')
})

flag = ws.write('拉勾教育', 'utf8', () => {
  console.log('ok3')
})

ws.on('drain', () => {
  console.log('drain')
})

pipe 方法的使用实现

const fs = require('fs')
const myReadStream = require('./ReadStream')

// const rs = fs.createReadStream('./f9.txt', {
//   highWaterMark: 4
// })

const rs = new myReadStream('./f9.txt')

const ws = fs.createWriteStream('./f10.txt')

rs.pipe(ws)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值