前提 Nodejs EventLoop
0.1 【堆】【栈】【队列】
堆栈队列
任何一种语言的运行环境都少不了【堆(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)
欢迎转载关注,后期会提供更多非基础内容,敬请期待。
注意:本文归作者所有,未经作者允许,不得转载