提 Nodejs EventLoop
0.1 【堆】【栈】【队列】

Node模块之Events模块(五)_java

堆栈队列

任何一种语言的运行环境都少不了【堆(heap)】【栈(Stack)】【队列 (queue) 】,JS也不例外。
JS的【临时变量以及调用时的形参】等等数据都是存储在【栈】中;
【堆】则是存储实际的【对象】,对象的【引用变量名(指针)】也是在【栈】;
而队列则是JS在实时运行环境中创建的【消息队列】或者【事件队列】。
JS是单线程,所以队列的实现让JS的异步处理有了可能性。

02 单线程、任务队列

尽信书不如无书,就喜欢这种有理有据的

摘录自【朴灵评注】JavaScript 运行机制详解:再谈Event Loop

为了避免复杂性,从一诞生,同一个时间只能做一件事。JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

异步执行的运行机制

(1)所有任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。系统把异步任务放到"任务队列"之中,然后继续执行后续的任务。
(3)一旦"执行栈"中的所有任务执行完毕,系统就会读取"任务队列"。如果这个时候,异步任务已经结束了等待状态,就会从"任务队列"进入执行栈,恢复执行。
(4)主线程不断重复上面的第三步。

上面这段初步地在说event loop。但是异步跟event loop其实没有关系。准确的讲,【event loop是实现异步的一种机制】

【上面提到的一系列的手段其实就是实现异步的方法,其中就包括event loop。以及轮询、事件等。】
【所谓轮询:就是你在收银台付钱之后,坐到位置上不停的问服务员你的菜做好了没。】
【所谓(事件):就是你在收银台付钱之后,你不用不停的问,饭菜做好了服务员会自己告诉你。】

【JavaScript运行环境的运行机制,不是JavaScript的运行机制。】

0.3 事件循环

首先我们看一下nodejs本质结构图

image.png

Node.js 通过 libuv 来处理与操作系统的交互,并且因此具备了【异步、非阻塞、事件驱动】的能力。Node.js 实际上是 Javascript 执行线程的单线程,真正的I/O 操作,底层 API 调用都是通过【多线程】执行的。

本质也就是:任务接收是单线程,任务执行是多线程。

那么也就是主要依靠【libuv】,那么本文主要介绍Nodejs事件模块,当然离不开原理,为何是这样?为何这么吊?为何又出现那么多回调函数?带着一些列问题搞明白了理论,至于代码,那也调用哪些大神写的API吧!

膜拜大神三秒钟

技术(艺术)源自生活、高于生活。对吧。

歪瓜仁真会玩

只要开始启动,那么这个姑娘就开始嗨起来了,其实我们事件循环也是这样的!从启动开始,就一直不停止的监听。

【论英文的重要性】

image.png

一图胜千言,但我还是就下图做个简单的梳理吧。我可不想被同为学渣的老铁们骂哈。

image.png

我们程序猿其实工作还是仅限于代码调用编写阶段,主要的核心在于理解内部原理,然后根据需求去编写(复制)代码。

讲个故事吧

JS单身狗(单线程)无异议,但它也是个贪心的黑心老板,在外边一直接活一直接活,根本无视
员工的死活,程序猿们为了改变世界(为了生存)不得不听产品经理的安排(程序猿心中的恶魔),
产品经理承上启下,一直分发任务(闲就不是代码狗了),而为了理想而奋战(别给我嘚瑟四点的晨曦,
老子刚下班)。世界越来越美好(为奋战的人祈祷安康)!

Nodejs中是怎么个情况,请看下图

event5.png

好了,结束了,接下来就是代码啦。

第一 Events模块概述

Events模块是Node对“发布/订阅”模式(publish/subscribe)的实现。一个对象通过这个模块,向另一个对象传递消息。

Node中的Event模块仅仅提供了一个对象: EventEmitter, EventEmitter 的核心就是事件触发与事件监听器功能的封装。

获取EventEmitter对象

//引用模块events, 点语法获取到EventEmitter
var EventEmitter = require('events').EventEmitter;
//初始化一个对象, 这个实例就是消息中心。
var emitter = new EventEmitter;
第二 EventEmitter 实例对象的方法

2.1 emitter.on(eventName, listener), 监听事件,如果触发就调用回调函数

1. eventName <String> | <Symbol>: 事件名称, 后边可以跟上函数;
2. listener <Function> : 回调函数;

2.2 emitter.emit(eventName[, ...args]), 根据eventName发送通知, 触发事件, 第一个参数为事件名称, 其余的参数会依次传入回调函数

以上两个方法的示例代码

var EventEmitter = require('events').EventEmitter;
var emitter = new EventEmitter();

//监听函数1, 事件名为--fun1
emitter.on('fun1', function(){
    console.log('触发函数1');
});
//定义有参数函数fun2, 
var fun2 = function(para){
    console.log('触发函数2, 参数为' + para);
};
//监听fun2
emitter.on('fun2', fun2);
//触发事件名
emitter.emit('fun1');
//触发事件名并且传参数
emitter.emit('fun2', 'event');

打印结果为:

$ node 2on.js 
触发函数1
触发函数2, 参数为event

2.3 emitter.once(eventName, listener), 类似on方法, 但回调函数只是执行一次

var EventEmitter = require('events').EventEmitter;
var emitter = new EventEmitter();

emitter.once('oncefun', function(){
    console.log('函数只执行一次');
});
emitter.emit('oncefun');
emitter.emit('oncefun');
console.log('--------')
emitter.on('fun', function(){
    console.log('函数');
});
emitter.emit('fun');
emitter.emit('fun');

执行的结果为:

$ node 3once.js 
函数只执行一次
函数
函数

虽然触发多次oncefun, 但依然打印一次;与下边形成对比;,

2.4 emitter.addListener(eventName, listener)类似于emitter.on(eventName, listener)

2.5 emitter.removeListener(eventName, listener), 移除监听

详细见实例代码:

var EventEmitter = require('events').EventEmitter;
var emitter = new EventEmitter();

//定义一个函数
var removeFun = function(){
    console.log('输出结果');
}
//以fun名称监听removeFun
emitter.on('fun', removeFun);
//每个30毫秒触发一次回调函数
setInterval(function(){
    emitter.emit('fun');
}, 30);
//200毫秒以后触发回调函数
setTimeout(function(){
    emitter.removeListener('fun', removeFun);
}, 200);

打印结果

$ node 5removeListener.js 
输出结果
输出结果
输出结果
输出结果
输出结果

#光标停止
第三部分 更多API详见

语法名称觉得挺好的, 见名知意. 更多的语法就看Node官网吧 -__-

Event: 'newListener'
Event: 'removeListener'
EventEmitter.listenerCount(emitter, eventName)
EventEmitter.defaultMaxListeners
emitter.addListener(eventName, listener)
emitter.emit(eventName[, ...args])
emitter.eventNames()
emitter.getMaxListeners()
emitter.listenerCount(eventName)
emitter.listeners(eventName)
emitter.on(eventName, listener)
emitter.once(eventName, listener)
emitter.prependListener(eventName, listener)
emitter.prependOnceListener(eventName, listener)
emitter.removeAllListeners([eventName])
emitter.removeListener(eventName, listener)
emitter.setMaxListeners(n)