在浏览器中,事件做为一个极为重要的机制,给予JavaScript响应用户操做与DOM变化的能力;在Node.js中,事件驱动模型则是其高并发能力的基础。html
学习JavaScript也须要了解它的运行平台,为了更好的理解JavaScript的事件模型,我打算从Node及浏览器引擎源码入手,分析其底层实现,并将个人分析整理为一系列博文;一方面做为笔记,另外一方面也但愿能与你们交流,分析和理解有疏漏偏颇之处,还望各位斧正。node
解释JavaScript事件模型自己的好文章已经不少了,能够说这已是一个说烂了的话题,这里我只简单写一下,而且提供一些好文章的连接。segmentfault
咱们的程序响应外部的事件有以下两种方式:windows
中断
操做系统处理键盘等硬件输入就是经过中断来进行的,这个方式的好处是即便没有多线程,咱们也能够放心地执行咱们的代码,CPU收到中断信号以后自动地转去执行相应的中断处理程序,处理完成后会恢复原来的代码的执行环境继续执行。这种方式须要硬件的支持,通常来讲都会被操做系统封装起来。api
轮询
循环检测是否有事件发生,若是有就去执行相应的处理程序。这在底层和上层的开发中都有应用。
Windows窗口程序就须要在主线程中写下以下代码,一般称作消息循环:浏览器
MSG msg = { }; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
消息循环不断检测是否有消息(用户的UI操做、系统消息等)出现,有的话就分发消息,调用相应的回调函数进行处理。
轮询方式的一个缺点就是:若是在主线程的消息循环里进行耗时操做,程序就没法及时响应新的消息。这在JavaScript中表现明显,之后还会提到这一点,并探讨其解决方案。多线程
然而JavaScript中并无相似消息循环代码,咱们只是简单地注册事件,而后等待被调用。这是由于浏览器、Node做为执行平台,已经将event loop实现了,JavaScript代码不须要介入到这个过程当中,只须要做为被调用者安静地等待便可。架构
知乎-关于浏览器处理事件的问题?匿名用户的回答:这个回答里图很不错,有助于理解event loop的工做原理;答案末尾有一些文章分享;并发
MDN - Concurrency model and Event Loop:MDN上对event loop的介绍。异步
Node采用V8做为JavaScript的执行引擎,同时使用libuv实现事件驱动式异步I/O。其事件循环就是采用了libuv的默认事件循环。
在src/node.cc中,
Environment* env = CreateEnvironment( node_isolate, uv_default_loop(), context, argc, argv, exec_argc, exec_argv);
这段代码创建了一个node执行环境,能够看到第三行的uv_default_loop()
,这是libuv库中的一个函数,它会初始化uv库自己以及其中的default_loop_struct
,并返回一个指向它的指针default_loop_ptr
。
以后,Node会载入执行环境并完成一些设置操做,而后启动event loop:
bool more; do { more = uv_run(env->event_loop(), UV_RUN_ONCE); if (more == false) { EmitBeforeExit(env); // Emit `beforeExit` if the loop became alive either after emitting // event, or after running some callbacks. more = uv_loop_alive(env->event_loop()); if (uv_run(env->event_loop(), UV_RUN_NOWAIT) != 0) more = true; } } while (more == true); code = EmitExit(env); RunAtExit(env); ...
more
用来标识是否进行下一轮循环。env->event_loop()
会返回以前保存在env
中的default_loop_ptr
,uv_run
函数将以指定的UV_RUN_ONCE
模式启动libuv的event loop。在这种模式下,uv_run
会至少处理一个事件:这意味着,若是当前事件队列中没有须要处理的I/O事件,uv_run
会阻塞住,直到有I/O事件须要处理,或者下一个定时器时间到。若是当前没有I/O事件也没有定时器事件,则uv_run
返回false。
接下来Node会根据more
的状况决定下一步操做:
若是more
为true
,则继续运行下一轮loop
。
若是more
为false
,说明已经没有等待处理的事件了,EmitBeforeExit(env);
触发进程的'beforeExit'
事件,检查并处理相应的处理函数,完成后直接跳出循环。
最后触发'exit'
事件,执行相应的回调函数,Node运行结束,后面会进行一些资源释放操做。
在libuv中,event loop会在每次循环的开始更新本身的time从而实现计时功能,而I/O事件则分为两类:
Network I/O是使用系统提供的非阻塞式I/O解决方案,例如在Linux上使用epoll,windows上使用IOCP。
文件操做和DNS操做没有(很好的)系统解决方案,所以libuv自建了线程池,在其中进行阻塞式I/O。
另外咱们也能够将自定义的函数抛到线程池中运行,在运行结束后主线程会执行相应的回调函数,不过Node并无将这一项功能加入到JavaScript中,也就是说只用原生Node是没法在JavaScript中开启新的线程进行并行执行的。
libuv Design Overview:关于libuv的架构及设计思路;
node child_process 'exit':Node的child_process 'exit'事件;
Node with threads:讨论了使用libuv线程池异步运行JavaScript代码。