【写给初入前端职场同学的话】,顺便聊聊:Node.js入门系列(四)事件处理机制及事件环机制

4,893 阅读22分钟

写在前面

在写这篇文章的时候,我收到两个同学的私信,在看了我之前的一些文章,觉得有收获,想跟我聊聊自己的一些经历和想法,希望能得到一些建议。两位小伙伴有相同的状况,我把情况总结归纳了一下:2020年毕业,由于今年特殊,没有了校招,目前人在深圳,刚入实习,公司规模小,不甘心低薪,想辞职回家,准备几个月进大厂。

我跟他们聊了很多,从技术到非技术,从角色转变到职场规则,在这里,我也想以自己工作几年的实际经历为基础,把这些话分享给大家,给与更多的同学一些参考,避免走错路、多绕弯,希望大家都能从里面找到自己需要的东西。

2020年是极其特殊的一年,由于特殊原因,很多同学失去了校招的机会,直接从校园进入了职场,而由于缺少了这个缓冲期,一部分同学进入了不是很理想的岗位。而前端,真正发展是从2013年开始,那个时候,只要HTML/JS/CSS就可以拿到满意职位,但从2018年开始,前端开始进入深水区,要求除了HTML/JS/CSS以外,还要会Node.js以及ES6等等;到2019年,要求会小程序开发,React、Vue、angular框架;到了2020年,逐渐开始深层次化和全面化,既要求有技术深度又要求有知识广度,被问起底层原理是常有的事。前端知识体系在被不断横向拓展的同时,公司也需要在某一领域有深入的见解的技术研发人员,越来越多的开发者开始高喊学不动了。在特殊原因,行业变化的双重加持下,我们怎么办?

如何从校园到职场?

离开校园进入社会,就意味着要和同行业所有人赛跑,上至即将退休的,下至打算入行的,而社会岗位是优胜劣汰制,需要的是最符合公司需求的,所以,思想上要从校园里“只要跟着大多数一起60分就好”的思维中转变过来,敏锐嗅觉行业发展方向,持续提升自我。

公司用的框架跟我会的不一样,我该怎么选框架?

首先明确一点,知识只是产出项目的工具,什么工具公司需要、符合需求、产量高,那就用什么工具,所以,考虑温饱的条件下,不要去深究冷门的工具,可以作为爱好,但不要作为职业。

开发框架上,公司需要用什么就学什么,如果你不喜欢这种框架,要会用公司框架,然后去行业探索主流的、你感兴趣的框架,选择一个深入研究,比如:选择React深入研究,但同时你得会用Vue和JQuery等等,要避免都会都不精的情况,随着行业的发展,公司越来越需要能在某个领域内解决实际问题的人才,岗位也会越来越细化,所以,在路越走越窄之前,提前做好准备,才能走到前面。

那么多人在分享技术文章,我觉得写的都很好,我都要看吗?

保持学习的态度是对的,但是要有选择性。相对目前自身能力来说,极度冷门、极度高深、用不着的文章,不看;看不懂的、偏激的文章不看;极度简易、极度没营养的文章不看。术业有专攻,有人在某个领域写的东西可能只对他自己有用,但对你,他可能分文不值。所以,只看适合自己的、对自己发展有帮助的,才会起到促进作用,那些用不着的,看不懂的,看了没有任何作用,看了就忘,浪费时间,而且还徒增学不会的焦躁。

初入职场薪资低怎么办?

如果刚入前端没有进入大厂,薪资低,不用自卑,薪资挂钩能力、资历、学历、情商,所以,要找到合适的方法,先让自己变得值钱,这才是正确的想法,你能为公司带来多少价值,你就能得到多高的“身价”认可。

为什么我学不动了?

一个根本原因:方法不对。怎么学呢?

没用的,要扔掉,记忆力空间有限,留给需要的;重要的,时常复习;很长时间学不会但是很重要的,先放弃,说明你根基不够,先去看相关基础;重要知识,不要停在表层,去深度研究,不要怕浪费时间,慢是为了快;新知识,类比记忆,举一反三,触类旁通;没时间,就挤时间,职场没有专门的学习时间;进度慢,换方法或找大佬,没有人主动教你怎么学;有惰性,设立计划,及时奖励,或找人同行,互相激励,共同进步。

慢下来,一点一点的去搭建自己的核心知识体系,盲目追逐热点,只会让自己疲于奔命,结果什么都懂一点,什么都不通;深入去学一个东西,学的不仅是知识点本身,同时也是在搭建自己的架构,走的快,不一定走的远,即使有一天转行了,不做程序了,懂得构建自己的核心竞争力,依然是最宝贵的品质。

一直看我写的文章同学会发现,每一篇我都会在开篇写点开发以外的知识,我希望的是,除了前端开发知识,还能学到其他东西,积攒思考方法和职场经验,提高自己的软实力。



限于文章篇幅和字数限制,在这里只能写这些,非常欢迎初入职场的小伙伴将自己的疑惑来与我交流,我在Node.js入门系列(一)特点、适用场景、解决的前端痛点问题中文末给出了方式。

下面就开始我们的Node系列五篇。

《诗经·大雅》中说:

靡不有初,鲜克有终

写系列的文章很多,但,一旦热情过了,没有人鼓励点赞和关注,后续就不了了之了,上一篇Node系列,点赞量远不如之前的几篇,我想其中一定是有什么原因,我也一直在反思原因,所以这篇,我反复思考怎样让大家看得懂,学的更透彻,了解更多一点,编稿时间长达九天,希望大家能有所收获,特别提醒,每一小节的小结一定要看,它是一小节中最有价值的部分,一定会让你豁然开朗的。

上集回顾

上集我们谈了:Node.js内部的console模块是如何进行开发调试的以及它跟REPL关系还有挂载到console上的方法,还聊了Node.js为我们准备的6个全局函数,模块相关的全局变量,以及在面试中常考的知识点。

如果首次翻到这篇文章,强烈建议大家从第一节开始读起,你将收获更多,这个系列没有夸大的标题,内容却有足够的广度,相信一定能让你看到不一样的解读。同时,每一篇字数都不多,5-10分钟可读完,如果暂时时间不充裕,可点击关注或赞,日后在“动态”中可找到此系列文。

请点击这里跳转需要阅读的章节:

漫谈Node.js入门
Node.js入门系列(一)特点、适用场景、解决的前端痛点问题
Node.js入门系列(二)模块、REPL Node.js入门系列(三)开发调试、全局内置函数和变量



JavaScript中的事件机制一直是前端面试中的一大难点,很多同学记过背过,最后还是忘了,而Node.js的高并发特性解决方案正式由于其独特的事件机制支撑的,那这个机制到底是什么?怎么运转的?我们就来详细看看:

本节学习目标

唯一的目标:
*)Node.js中的事件处理机制和事件环机制跟前端有啥区别?

本节学习目录

一 Node.js中的事件处理机制

二 Node.js中的事件环机制

三 本节相关面试考点

四 本节小结

五 文末

一 Node.js中的事件处理机制

Node.js使得JavaScript可以在服务端运行,同时它也延续了JavaScript事件驱动的驱动方式。这也是为什么要在这里详细讨论事件处理机制的原因,因为事件是驱动Node.js内部运转的重要机制,举足轻重。

要搞明白事件处理机制,那我首先得先弄清楚什么是事件吧?然后才是弄清楚怎么处理事件的。

我翻阅了很多权威指南,大多数上来就是将Node.js事件处理API怎么用的,对初学者很不友好,API照着文档就能用,探索知识的前因后果才是一个优秀开发工程师该有的品质。

什么叫事件?

MDN对事件的解释是:

事件是您在编程时系统内发生的动作或者发生的事情,系统响应事件后,如果需要,您可以某种方式对事件做出回应。

每个可用的事件都会有一个事件处理器,也就是事件触发时会运行的代码块。当我们定义了一个用来回应事件被激发的代码块的时候,我们说我们注册了一个事件处理器。

注意事件处理器有时候被叫做事件监听器——从我们的用意来看这两个名字是相同的,尽管严格地来说这块代码既监听也处理事件。监听器留意事件是否发生,然后处理器就是对事件发生做出的回应。

说到这里,先拓展一下,前端事件按类别分有哪些?思考10秒钟~

前端事件有哪些?

我简单总结了一下,大致可分为以下四类:鼠标事件、键盘事件、客户端事件、DOM事件。
鼠标、键盘的事件不用说,客户端事件简单说下,对于浏览器它包含加载、卸载、滚动、放缩、失焦、获焦等,而对于移动端或iPad设备,还包含了手势事件和声音事件等等;DOM事件就是我们常见的文本被选中、失焦、获焦、输入事件、change事件等。

那前端事件我大概知道了,服务器端事件呢?事件类型又有哪些?

Node.js中的事件是什么?

在Node.js中,事件主要是在模块对象与模块对象直接产生的,例如:接收到客户端请求、产生连接错误等等。

无论是前端事件还是后端事件,你会发现,这些Node.js中的事件也是符合事件的三要素原则的,三要素原则指的是:事件源、事件动作、事件处理程序。打个比方:我 打开开关 灯亮了。这一整个过程就称为事件,事件源是我,表示事件的发起者,事件动作是打开开关,表示行为,一般是动词,灯亮了是事件处理程序,它表示作用目标对象对事件动作的响应。
转换成伪代码,可以写成这样:

事件源.事件 = function() {

  事件处理函数
}

思维拉回来,本节的标题是:Node.js中的事件处理机制,对照事件的三大要素,它其实对应的第三个元素:事件处理程序。

我们从三大要素的第一个要素说起:事件源。

Node.js中,定义了一个处理事件的模块,这个模块叫event,这个模块中有一个类,EventEmitter。

所有能触发事件的对象(也就是三要素的第一个:事件源),都继承自EventEmitter,也就是说,所有能触发事件的对象都是EventEmitter的实例。
一分钟时间,再读两遍上面两句话。
我画了一个简易的图,方便理解他们之间的关系:


好了,我们现在清楚了在Node.js中谁能触发事件了,这些事件触发者都是清一色的对象实例,并且呢,这些实例都继承都同一个类----EventEmitter,而EventEmitter类又是event模块内部的。

看到这里,有小伙伴有没有感觉到很疑惑:Node.js触发事件就触发呗,还要通过继承一个类生成实例,然后才能触发事件,为啥不省略EventEmitter呢,非要搞得这么麻烦,明明直接可以把触发事件放event模块内部就行,你看人家JavaScript就是,直接在全局放事件,不需要继承来继承去的?

如果你有这种感觉,恭喜,你开始懂得如何去高效学习了,我们先不看答案,先带着问题阅读下面的内容,在本节小结的地方,我相信你一定会有自己的答案,在那里,我将公布答案。

好了,先小结一下,前端中的事件和Node.js事件的相同点和区别:

1)前端事件来源很多,而之所以会很多是因为涉及到的角色太多了,像鼠标键盘、浏览器、DOM等等,每一个角色都提供了事件,所以配合起来事件来源既多又杂;
2)Node.js能触发事件的角色就一种,单一明确;
3)Node.js和JavaScript都是通过事件驱动的,事件是驱动内部运转的重要机制。

接下来我们来详细看看能诞生触发事件对象的类----EventEmitter。

EventEmitter

在events模块中,为EventEmitter类本身定义了两个事件:

newListener事件与removeListener事件

任何时候,当对继承了EventEmitter类的实例对象绑定事件处理函数时,都讲触发EventEmitter类的newListener事件。这就意味着:我们可以随时监听到子类对象绑定事件操作,进而采取相应的处理逻辑。

其用法如下:

emitter.on('newListener', function (eventName, func){

    事件处理函数
});

// 注意:emitter代表是EventEmitter的实例
// 两个方法都接收两个参数,eventName是字符串类型的事件方法名,func是一个函数

当实例绑定事件的时候我们可以通过newListener监听到,那取消绑定的时候有办法坚挺到吗?有。谁?removeListener

其用法如下:

emitter.on('removeListener', function (eventName, func){

    事件处理函数
});

// 注意:emitter代表是EventEmitter的实例
// 两个方法都接收两个参数,eventName是字符串类型的事件方法名,func是一个函数

listenerCount

EventEmitter类自身还拥有一个listenerCount方法,可用来获取某个对象的指定事 件的事件处理函数的数量,其用法如下:

EventEmitter.listenerCount (emitter, event)

在listenerCount方法中,使用两个参数,其中第一个参数用于指定需要获取哪个对象的 事件处理函数的数量,第二个参数用于指定需要获取哪个事件的事件处理函数的数量。



除了EventEmitter本身的方法,Node.js在这个类上挂载了很多方法,先概览一下有哪些:

简单描述一下这些方法的作用:

EventEmitter 实例上的方法

addListener和on

addListener和on方法作用一样,内部实现原理一样,参数一样,唯一不一样的就是他们的名字不一样,作用是:对某个时间绑定处理函数。其用法如下:

emitter.on(eventName, listener)

emitter.addListener(eventName, listener)

// 注意:emitter代表是EventEmitter的实例
// 两个方法都接收两个参数,eventName是字符串类型的事件方法名,listener是一个函数

这两个方法使用跟JQuery中的事件绑定方式极其相似,记忆时类比记忆即可。熟悉JQuery的同学都知道,JQuery中的on方法,是可以对某个元素绑定多种事件的,在Node.js中,on方法同样可以绑定多个事件处理函数,addListener也可以绑定多个事件处理函数。

once

EventEmitter类的once方法与on方法类似,作用均为对指定事件绑定事件处理函数,区别在于,当事件处理函数执行一次后立即被解除,即该事件处理函数只会被执行一次。once方法所用参数也与on方法所用参数相同,其用法如下:

emitter.once (eventName listener)

// 注意:emitter代表是EventEmitter的实例,listener是一个函数
setMaxListeners

上面说到on方法可以实现绑定多个事件处理函数,那最多能绑多少个呢?能不能让我手动控制能绑几个?

setMaxListeners方法可以限制对于同一个事件做多可以绑几个事件,语法如下:

emitter.setMaxListeners(n)

// 注意:emitter代表是EventEmitter的实例
// n是一个正整数
listeners

当需要取得一个指定事件的所有事件处理函数时,可以使用listeners方法,方法如下所示:

emitter.listeners(eventName)

// 注意:emitter代表是EventEmitter的实例
// eventName为字符串,listeners的返回值是一个数组

listeners方法使用一个参数,参数值为指定事件名。该方法返回由该事件的所有事件处 理函数构成的数组。

removeListener

当需要对某个事件解除某个事件处理函数时,可以使用EventEmitter类的removeListener方法,其用法如下:

emitter.removeListener (eventName, listener)

// 注意:emitter代表是EventEmitter的实例

与addListener方法类似,EventEmitter类的removeListener方法也使用两个参数,其中第一个参数为指定事件名,第二个参数为该事件的事件处理函数。

removeAllListeners

另外,可以使用EventEmitter类的removeAllListeners方法取消某个事件的所有已被指定事件处理函数或所有已被指定的事件处理函数,其用法如下:

emitter.removeAllListeners ( [eventName])

// 注意:emitter代表是EventEmitter的实例

在removeAllListeners方法中,可以使用一个参数,参数值为需要被解除事件处理函数的事件名。
如果在removeAllListeners方法中使用事件名参数时,将取消该事件的所有事件处理函数;
如果在removeAllListeners方法中不使用事件名参数时,将取消所有已被指定的事件。

emit

当你需要手工触发某个对象的一个事件时,可以使用EventEmitter类的emit方法,其用法如下:

emitter.emit(eventName, [arg1], [arg2][...])

// 注意:emitter代表是EventEmitter的实例

在emit方法中可以使用一个或多个参数,其中第一个参数值为需要手工触发的事件名,从第二个参数开始为需要传递给事件处理函数的参数。

小结

到目前为止,我们讨论了Node.js中事件触发三要素的第一要素----事件源的相关知识,无论是生成实例的类身上的方法,又或者实例本身上的方法,有了这些方法我们能做到对所有事件增删查。读到这里了,本节开始时预留的问题是不是有了答案?---Node.js为什么要在事件触发对象外包一层类EventEmitter?我们一起来总结下:

1)EventEmitter这个类可是有很高权限的,它能知道谁绑定新事件,谁取消了绑定,还能知道某个对象针对某个事件绑定了几个事件,正可谓,事件触发对象的一举一动都在EventEmitter的掌控之下。

2)我们再看看前端,在早期,如果我想知道某个元素上绑定了几个事件,有API可以直接获取到吗?没有。当然了,可以通过其他手段获取到,如:所有事件都通过属性的方式写在DOM上,通过获取DOM属性就可以知道绑定了哪些属性;又或者是在FireFox通过开发者工具审查元素可以看到某个元素绑定了哪些事件,等等方法。这些方法都是间接通过我们的智慧得到的,又或者浏览器发现了开发者这个需求,但其他事件提供者又没提供直接API,所以浏览器补上了这个缺点,总之一句话,没有API可以直接管控所有事件,所有的事件都散落在全局。从这对比也可以看出,Node.js对事件的底层处理态度不愧是号称“万物皆模块”的,将这个概念贯穿到了每一个角落。

3)Node.js在提供EventEmitter这个类进行监管的时候,也给了事件触发对象足够的权限空间去处理自己的事情,无论是增删还是改查,EventEmitter这个类都不进行干预,很好的分清了类和实例的角色关系。当然,这种设计理念仅仅是Node.js使用后端语言,却作为前端框架备受推崇的原因之一,它的很多优点,会随着我们后续系列篇的探讨逐渐揭开。

二 Node.js的事件环机制

在前端发展初期,网页,只是用来给用户展示信息的,简单的用户与服务端之间的交互我可以通过form表单输入,最后通过submit属性去提交,没有JavaScript什么事。

到后来,网络开发者就想,网络速度本来就慢,用户填写表单后不管对不对都要直接抛给服务端,服务端判断不合格再抛回来,如此往复,不仅让用户不耐烦,还浪费网络资源,如果能在发送表单信息之前验证一下,合格之后再提交给服务端就好了。

于是,JavaScript就带着解决表单验证的使命出现了,HTML为了把JavaScript这个后来的小老弟用到页面中,想了很多办法,最终决定在页面中为JavaScript留一个标签元素,这个标签就是script标签,小老弟JavaScript就在script这个标签里面玩,外面就不要出来了,出来我就不认为你是JavaScript了。

我们现在反过来看前端的发展过程,如果没有交互需求,也就不需要JavaScript,大家都只安安静静的看静态页面就好了。后来我们之所以需要JavaScript,是为了处理交互逻辑。

再回到Node.js服务端,没有了用户界面,自然没有了来自用户的交互,Node.js中的交互来自于服务器应用程序之间的交互。那究竟什么是Node.js的事件环机制呢?

还是举个好理解的例子:

事件环就如同一个邮递员,而每个事件就好比是邮递员需要送达的一封邮件。他手上有大量需要依序送达的邮件,而他需要按照指定路线来送达这些邮件,而回调函数就好比这些路线,由于邮递员只有一双腿,所以他每次只能按照指定路线来送达一封邮件,也就是说,他每次只能处理一个回调函数。在他按照某条指定路线送达某封邮件的途中,可能会有人给他新的邮件,这就是代码中要求他处理的新的事件。这种情况下,邮递员将会转而处理新的事件(包括触发事件、初始化该事件的回调函数等),在该事件处理完毕之后,转而送达原本要送达的邮件,也就是说,在回调函数的执行过程中,他将转而处理新的事件,在该事件处理完毕之后,转而继续处理原回调函数。这种环状处理机制,在Node.js中称为事件环机制。

在Node.js中,由于采用了事件环机制,所以在设计之初,设计者几乎为所有事件都设置了回调函数,目的就是为了满足Node.js这种非阻塞的事件环机制。

事件环机制里有几个比较重要的概念值得注意:

1)是需要按照顺序送达的邮件,翻译过来就是,多个有序的待执行任务;
2)一次只能送一封,翻译过来就是,同一时刻只能执行一个任务;
3)有人给他新的邮件,邮递员将会转而处理新的事件,翻译过来就是,当有新任务插入时,原任务暂停,转而执行新任务;
4)在该事件处理完毕之后,转而送达原本要送达的邮件,翻译过来就是,在新任务处理完毕之后,转而继续处理原回调函数。

拓展

在上面举例事件环机制中,我们会发现,这个邮递员在一直跑,中途没有停下来等待(阻塞)的时候,对照在Node.js中,也确实是这样,Node.js事件环机制使得多个事件能够有序不停的执行,没有了阻塞,这就是Node.js又一大优点:高效的内存利用和优秀的事件调度机制。

三 本节相关面试考点

1)Node.js事件触发对象有哪些方法?
2)详细讲讲Node.js事件机制是怎样的?
3)Node.js事件机制和JavaScript事件机制有什么区别?
4)Node.js非阻塞和高效调度事件的底层原因是什么?
5)你认为Node.js作为前端框架为什么备受推崇?

四 本节小结

到这里,我们就讲清楚了Node.js中事件是如何产生的,什么情况能触发事件,如何对事件进行增删改查,以及当有多个事件需要执行的时候是如何进行高效调度的,这几个问题现在应该都没有疑问了。
这篇文章是前后关联的,建议静下心来跟着文章思路阅读,一定有所收获。

五 文末

文章从整理到发布共计10天时间,只为能够在一篇文章里学到多个知识,文章需要支持,敬请点个赞,平台会为你推荐更多优质的文章。

前端格局,带你看有深度的前端世界,搭建属于自己的知识架构,关注我,跑的更快一点。