最近开始看起了 Node.js 入门的书,开始学习一下 Node.js 的相关知识,以下是我做的一点笔记,希望对自己和读者有所帮助。
Node.js的特点
Node.js 最大的特点是采用异步 I/O 和事件驱动架构设计,这种架构设计巧妙避开了原先 Apache Server 的多线程的思路,直接使用了单线程,从而具有了高并发开发的特点。传统的多线程模型是每个业务逻辑提供一个系统的线程,通过系统线程的切换弥补 I/O 在时间调用上的消耗。Node.js 采用单线程模型,对于所有的 I/O 都采用异步请求,从而避免因上下文频繁切换而导致的耗时操作。Node.js 在执行过程中还会使用一个异步队列,通过不断的循环队列从而等待程序进程的处理。
Node.js 异步操作是基于事件实现的,所有涉及耗时的操作,如磁盘 I/O、网络通信、数据库查询都是基于事件,而非阻塞实现的,其实现过程如图1所示。
图1 Node.js 事件与回调的过程
其核心拥有一个事件循环,所以判断是否有事件,如果有事件,则取出相应的事件然后进行执行,执行完毕以后再次判断是否有关联的回调,如果有回调则执行相应的回调,如果没有回调则至此该事件执行完毕。
这种 Node.js 的事件循环方式,可以增加 Node.js 应用程序运行的健壮性以及可用性。
但同时,这种异步基于事件的编程方式其弊端也是显而易见的,因为其不符合开发者正常的开发思路,会造成开发艰难,容易出错,不利于开发应用。
由于 Node.js 采用了异步 I/O 和事件驱动,虽然不利于开发,但其在性能上的提升是显而易见的。
Node.js与V8引擎
V8 引擎采用的是即时编译技术(Just In Time),即JIT,直接将 JavaScript 代码编译成本地平台的机器码。从宏观上看,V8 引擎编译如图2所示。
图2 V8 引擎编译过程(宏观)
在该过程中,首先将源码编译成抽象语法树(Abstract Syntax Tree),即AST,然后再编译成为本地机器码,并且后一个步骤只依赖前一个步骤。V8 引擎不需要把编码编译成字节码,即程序开始运行后,直接解释代码并交给 CPU 运行,但这种运行方式也使代码优化的方式变得更加困难。
当 JavaScript 代码直接交给浏览器或者 Node 执行时,底层 CPU 并不认识相关的代码,它只认识自己的指令集。指令集对应的是汇编代码,其代码如下:
functiuon factorial(N) {
if (N === 1) {
return 1;
} else {
return N * factorial(N - 1);
}
}
但是用汇编语言来写,大概需要300行代码。这时就需要使用 JavaScript 引擎编译成汇编代码,交给 CPU 执行。
在众多的 JavaScript 引擎中,最重要的是 V8 引擎。 V8 引擎由许多子模块构成,是一个相当复杂的项目,其编译过程如下。
- Paser(分析器):负责将 JavaScript 源码转换成 Abstract Syntax Tree(AST)。
- Ignition(解释器):负责把 AST 转换成 Bytecode(字节码),并进行解释执行;同时收集 TurboFan 优化编译器所需要的信息,如函数参数类型等。
- TurboFan(编译器):利用 Ignition 收集的信息,把 Bytecode 转换成优化的汇编代码。
首先,获取 JS 代码,然后将 JS 代码转换为 AST 语法树,AST 语法树再转换为字节码,字节码直接送入解释器执行,同时监控模块识别是否是热代码,如果是热代码,将会在下一步优化编译的时候被修改、被优化。经过几次的修改优化,最终形成机器码,送入 CPU 执行,并输出结果给用户。V8 引擎的运行流程如图3。
本文章重点描述了 Node.js 的特点,Node.js 与 V8 引擎,希望我自己和大家都能从中学习到知识,为以后学习并使用 Node.js 奠定基础。
我是捞佬,我们下次再见!