nodejs中的v8引擎模块
一、promise钩子模块;
二、垃圾回收模块;
1、promise钩子包括:
(1)onInit(callback) 在promise创建时调用;
(2)onSettled(callback) 在fulfilled或rejected时调用;
(3)onBefore(callback) 在promise继续执行之前调用的回调;
(4)onAfter(callback) 在promise继续执行之后调用的回调;
const { promiseHooks } = require('v8')
// 1、创建一个钩子函数,在Promise对象初始化时输出日志;
const initHook = () => {
console.log('Promise 对象被创建')
}
// 注册钩子函数
promiseHooks.onInit(initHook)
// 2、定义一个settle钩子函数
const settledHook = () => {
console.log('当promise对象被settled(即fulfilled或rejected)时触发;')
}
// 注册钩子函数
promiseHooks.onSettled(settledHook)
// 3、定义一个before钩子函数
const beforeHook = () => {
console.log('Promise 对象 在 promise 继续执行之前调用的回调;')
}
promiseHooks.onBefore(beforeHook)
// 4、定义一个before钩子函数
const afterHook = () => {
console.log('Promise 对象 在 promise 继续执行后调用的回调;')
}
promiseHooks.onAfter(afterHook)
let promiseObj = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise resolved')
}, 2000);
})
promiseObj.then((result) => {
console.log(result)
})
运行的结果:
2、v8的垃圾回收
垃圾回收包括3种回收算法:
(1)引用计数法:js维护的引用表记录了内存的引用次数,当值为0时,被GC回收,它虽然能在发现垃圾时及时清除,但无法处理循环引用的对象内存;
(2)标记清除法:遍历当前对象是否可以访问,如果不能则添加标记然后清除,它遍历的均为可达对象而不是像循环引用的局部对象,因此可以解决循环引用问题,但其标记和清除是在不同阶段执行的,不能立即清除;
(3)分代算法:堆内存中存在新生代和老生代2种空间,在新生代,新创建的对象都会被分配到from区域,在GC阶段,将from区域中存活的数据复制到2区域,2区域再进行交互,实现一次垃圾回收,如果在两次gc中都还存活的数据就会被晋升到老生代区域存储。
const v8 = require('v8')
// 启用GCprofile
v8.setFlagsFromString('--trace_gc');
// 模拟创建大量对象并释放内存
const createObject = () => {
const object = []
for (let i = 0; i < 100000; i++) {
object.push({})
}
}
createObject()
// 等待一段时间,让垃圾回收产生
setTimeout(() => {
v8.setFlagsFromString('--notrace_gc')
}, 5000);
运行的结果:
nodejs中EventEmitter
1、EventEmitter实现
// 自己实现一个eventEmitter
/**
* 1、注册
* 2、触发
* 3、移除
* 4、测试
*/
class EventEmitter {
constructor() {
this._event = {}
}
// 注册
on (type, listener) {
let events = this._event
let existing = events[type]
if (existing === undefined) {
events[type] = [listener]
} else {
// 事件存在则追加
events[type] = [...existing, listener]
}
return this
}
// 触发
emit (type, ...args) {
// 找到当前触发的回调函数
const handler = this._event[type]
console.log('handler', handler)
if (!handler) {
console.log(`event ${type} noting existing!`)
} else {
// 存在当前事件
const length = handler.length
// 当前事件只有一个
if (length === 1) {
// 直接触发回调函数
handler[0](args)
} else if (length > 1) {
// 当前事件有多个回调函数
// 遍历该事件所有回调函数,依次触发
for (let i = 0; i < length; i++) {
handler[i](args)
}
} else {
return this
}
}
}
// 移除监听
off (type, listener) {
let list = this._event[type]
if (!list) {
// 事件不存在
console.log(`event ${type} is not existing!`)
} else {
let length = list.length
let postion
if (length === 1) {
delete this._event[type]
} else {
// 要删除的事件存在多个监听
// 找出要删除的事件的下标
for (let i = 0; i < length; i++) {
if (list[i] + '' === listener + '') {
postion = i
break
} else {
postion = -1
}
}
if (postion != -1) {
delete this._event[type].splice(postion, 1)
} else {
this._event[type].pop()
}
}
}
}
}
let emitter = new EventEmitter()
emitter.on('data', (data) => {
console.log('第一个订阅data事件的对象:', data)
})
let handler = (data) => {
console.log('第二个订阅data事件的对象', data)
}
emitter.on('data', handler)
emitter.on('data', (data) => {
console.log('第三个订阅data事件的对象', data)
})
emitter.off('data', handler)
emitter.emit('data', 'hello world')
运行结果
node事件循环
尽管js是单线程的,但是可以通过将异步操作转移到系统内核去完成从而实现非阻塞的机制称为事件循环。
因为现代内核都是多线程的,它们可以在后台执行多个操作,当某个操作完成时,内核通知Node.js,将对应的回调添加到轮询队列中。
事件循环的六个阶段:
1、timer阶段,执行setTimeout()和setInterval()
2、Pending和callbacks阶段:执行推迟到下一个循环迭代的I/O回调。
3、idle、prepare阶段,内部使用
4、poll阶段:执行I/O操作
5、check阶段:执行setImmediate()
6、close callbacks阶段:关闭回调
在每次运行事件循环时,Node.js都会检查它是否在等待任何异步I/O或定时器,如果没有,则会关闭。
使用process.nextTick的原因:
1、允许用户处理错误,清除任何不需要的资源,或者在事件循环继续之前重试请求
2、允许回调在同步之后,但在事件循环之前运行