1、node.js 和 JavaScript
不知道有没有人和我一样分不清楚node.js和JavaScript的关系的人,尤其是对node.js有一点点了解的,比如当我知道console.log()是node.js的Console类的时候我就更分不清它俩的关系了。我来理一理他们的关系:
JavaScript是一门语言,node.js不是一门语言,它仅仅就是用于运行普通JavaScript代码的东西。node.js诞生之前,JavaScript只能运行于浏览器,现在也可以在服务器端运行于node.js。
2、node.js和v8引擎
所有浏览器都有运行网页上JavaScript的JavaScript引擎。Firefox有叫做Spidermonkey的引擎,Safari有JavaScriptCore,Chrome有V8。node.js的创造者受到Chrome和V8的启发,Node.js很快就面世了。node.js就是带有能操作I/O和网络库的V8引擎,因此你能够在浏览器之外使用JavaScript创建shell脚本和后台服务或者运行在硬件上
关于v8:它使用C++写成,它革命性的创举是将Javascript源码预编译为机器码,而不是像以前那样将Javascript翻译为字节码,然后在运行时使用JIT动态执行代码
node.js和V8的关系:node.js=V8+内置基本模块(大多用JavaScript编写),类似JRE=JVM+java标准库。
3、node.js内部结构
从Node.js 的内部结构图,我们可以看到,自底向上主要可以分成三层:最底层是 Node.js 依赖的各种库,有 V8、libuv 等;中间层是各种 Binding,也就是胶水代码;最上层是应用代码,可使用 Node.js 的各种 API。
底层依赖:
(1)V8:Google 开源的高性能 JavaScript 引擎,它将 JavaScript 代码转换成机器码,然后执行,因此速度非常快。V8 以 C++ 语言开发。
(2)libuv:libuv 以 C 语言开发,内部管理着一个线程池。在此基础之上,提供事件循环(Event Loop)、异步网络 I/O、文件系统 I/O等能力。
(3)其他底层依赖:如 c-ares、crypto (OpenSSL)、http-parser 以及 zlib。这些依赖提供了对系统底层功能的访问,包括网络、压缩、加密等。
中间层:
(1)Binding:Node.js 底层的依赖库,有的以 C 语言开发,有的以 C++ 语言开发,如何让应用代码(JavaScript)能够与这些底层库相互调用呢?这就需要中间层的 Binding 来完成。Binding 是一些胶水代码,能够把不同语言绑定在一起使其能够互相沟通。在 Node.js 中,binding 所做的就是把 Node.js 那些用 C/C++ 写的库接口暴露给 JS 环境。
(2)Addon:中间层中,除了 Binding,还有 Addon。Binding 仅桥接 Node.js 核心库的一些依赖,如果你想在应用程序中包含其他第三方或者你自己的 C/C++ 库的话,需要自己完成这部分胶水代码。你写的这部分胶水代码就称为 Addon。本质上都是完成桥接的作用,使得应用与底层库能够互通有无。
应用层:
应用层的代码,我们开发的应用、npm 安装的包都运行在这里。
4、node.js的特点
(1)单线程:不同于Java、PHP或者.net等服务器端语言中,会为每一个客户端连接创建一个新的线程。而node.js不为每个客户连接创建一个新的线程,而仅仅使用一个线程。当有用户连接了,就触发一个内部事件,通过非阻塞I/O、事件驱动机制,让Node.js程序宏观上也是并行的。
(2)非阻塞I/O:
Apache请求数据库的代码:
在传统的单线程处理机制中,在执行了访问数据库代码之后,整个线程都将暂停下来,等待数据库返回结果,才能执行后面的代码。I/O阻塞了代码的执行,极大地降低了程序的执行效率。
node.js请求数据库代码:
非阻塞型I/O机制,因此在执行了访问数据库的代码之后,将立即转而执行其后面的代码,把数据库返回结果的处理代码放在回调函数中,从而提高了程序的执行效率。
(3)事件驱动 event-driven:
在Node中,客户端请求建立连接,提交数据等行为,会触发相应的事件。Node在一个时刻,只能执行一个事件回调函数,但是在执行一个事件回调函数的中途,可以转而处理其他事件(比如,又有新用户连接了),然后返回继续执行原事件的回调函数,这种处理机制,称为“事件环”机制。
一个 Node.js 应用启动——>V8 引擎会执行应用代码——>Binding 把 Node.js 中用 C/C++ 写的库接口暴露给 JS 环境——>(事件发生时)回调函数会被加进一个事件队列——>事件循环会持续把回调函数从队列中拿出并执行——>文件系统的I/O 请求和 DNS 相关请求都会放进libuv线程池处理(其他的请求,如网络、平台特性相关的请求会分发给相应的系统处理单元进行处理)——>完成之后触发相应事件,对应的事件回调函数会被放入事件队列。
总结:事件循环机制保证了node.js的单线程、非阻塞I/O两大特性得以实现。
参考文档: