正如当年为了统一JavaScript 语言标准,人们制定了ECMAScript 规范一样,如今为了统一JavaScript 在浏览器之外的实现,CommonJS 诞生了。CommonJS 试图定义一套普通应用程序使用的API,从而填补JavaScript 标准库过于简单的不足。CommonJS 的终极目标是制定一个像C++ 标准库一样的规范,使得基于CommonJS API 的应用程序可以在不同的环下运行,就像用C++ 编写的应用程序可以使用不同的编译器和运行时函数库一样。为了保持中立,CommonJS 不参与标准库实现,其实现交给像Node.js 之类的项目来完成。Node.js 是目前CommonJS 规范最热门的一个实现,它基于CommonJS 的Modules/1.0 规范实现了Node.js 的模块,同时随着CommonJS 规范的更新,Node.js 也在不断跟进。
快速搭建 Node.js / io.js 开发环境以及加速 npm
Cmd node 连续两次ctrl+c+c退出
mkdir 创建目录 touch 新建文件 node test.js运行
什么是阻塞(block)呢?线程在执行中如果遇到磁盘读写或网络通信(统称为 I/O 操作),通常要耗费较长的时间,这时操作系统会剥夺这个线程的 CPU 控制权,使其暂停执行,同时将资源让给其他的工作线程,这种线程调度方式称为 阻塞。当 I/O 操作完毕时,操作系统将这个线程的阻塞状态解除,恢复其对CPU的控制权,令其继续执行。这种 I/O 模式就是通常的同步式 I/O(Synchronous I/O)或阻塞式 I/O (Blocking I/O)。
相应地,异步式 I/O (Asynchronous I/O)或非阻塞式 I/O (Non-blocking I/O)则针对所有 I/O 操作不采用阻塞的策略。当线程遇到 I/O 操作时,不会以阻塞的方式等待 I/O 操作的完成或数据的返回,而只是将 I/O 请求发送给操作系统,继续执行下一条语句。当操作系统完成 I/O 操作时,以事件的形式通知执行 I/O 操作的线程,线程会在特定时候处理这个事件。为了处理异步 I/O,线程必须有事件循环,不断地检查有没有未处理的事件,依次予以处理。
阻塞模式下,一个线程只能处理一项任务,要想提高吞吐量必须通过多线程。而非阻塞模式下,一个线程永远在执行计算操作,这个线程所使用的 CPU 核心利用率永远是 100%,I/O 以事件的方式通知。在阻塞模式下,多线程往往能提高系统吞吐量,因为一个线程阻塞时还有其他线程在工作,多线程可以让 CPU 资源不被阻塞中的线程浪费。而在非阻塞模式下,线程不会被 I/O 阻塞,永远在利用 CPU。多线程带来的好处仅仅是在多核 CPU 的情况下利用更多的核,而Node.js的单线程也能带来同样的好处。这就是为什么 Node.js 使用了单线程、非阻塞的事件编程模式。
单线程事件驱动的异步式 I/O 比传统的多线程阻塞式 I/O 究竟好在哪里呢?简而言之,异步式 I/O 就是少了多线程的开销。对操作系统来说,创建一个线程的代价是十分昂贵的,需要给它分配内存、列入调度,同时在线程切换的时候还要执行内存换页, CPU 的缓存被清空,切换回来的时候还要重新从内存中读取信息,破坏了数据的局部性。
当然,异步式编程的缺点在于不符合人们一般的程序设计思维,容易让控制流变得晦涩难懂,给编码和调试都带来不小的困难。习惯传统编程模式的开发者在刚刚接触到大规模的异步式应用时往往会无所适从,但慢慢习惯以后会好很多。尽管如此,异步式编程还是较为困难,不过可喜的是现在已经有了不少专门解决异步式编程问题的库(如async) 。
Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。在开发者看来,事件由 EventEmitter 对象提供
模块是 Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个
Node.js 文件就是一个模块,这个文件可能是 JavaScript 代码、 JSON 或者编译过的 C/C++ 扩展。在前面章节的例子中,我们曾经用到了 var http = require('http'), 其中 http是 Node.js 的一个核心模块,其内部是用 C++ 实现的,外部用 JavaScript 封装。我们通过require 函数获取了这个模块,然后才能使用其中的对象。
包是在模块基础上更深一步的抽象, Node.js 的包类似于 C/C++ 的函数库或者 Java/.Net的类库。它将某个独立的功能封装起来,用于发布、更新、依赖管理和版本控制。 Node.js 根据 CommonJS 规范实现了包机制,开发了 npm来解决包的发布和获取需求。Node.js 的包是一个目录,其中包含一个 JSON 格式的包说明文件 package.json。严格符合 CommonJS 规范的包应该具备以下特征:
package.json 必须在包的顶层目录下;
二进制文件应该在 bin 目录下;
JavaScript 代码应该在 lib 目录下;
文档应该在 doc 目录下;
单元测试应该在 test 目录下。
Node.js 对包的要求并没有这么严格,只要顶层目录下有 package.json,并符合一些规范即可。当然为了提高兼容性,我们还是建议你在制作包的时候,严格遵守 CommonJS 规范。
在使用npm 安装包的时候,有两种模式:本地模式和全局模式。默认情况下我们使用npm install命令就是采用本地模式,即把包安装到当前目录的node_modules 子目录下。Node.js的require 在加载模块时会尝试搜寻node_modules 子目录,因此使用npm 本地模式安装的包可以直接被引用。
npm 还有另一种不同的安装模式被成为全局模式,使用方法为:
npm [install/i] -g [package_name]
与本地模式的不同之处就在于多了一个参数-g。我们在介绍supervisor那个小节中使用了npm install -g supervisor 命令,就是以全局模式安装supervisor。
为什么要使用全局模式呢?多数时候并不是因为许多程序都有可能用到它,为了减少多重副本而使用全局模式,而是因为本地模式不会注册PATH 环境变量。举例说明,我们安装supervisor 是为了在命令行中运行它,譬如直接运行supervisor script.js,这时就需要在PATH环境变量中注册supervisor。npm 本地模式仅仅是把包安装到node_modules 子目录下,其中的bin 目录没有包含在PATH 环境变量中,不能直接在命令行中调用。而当我们使用全局模式安装时,npm 会将包安装到系统目录,譬如/usr/local/lib/node_modules/,同时package.json 文件中bin 字段包含的文件会被链接到/usr/local/bin/。/usr/local/bin/ 是在PATH 环境变量中默认定义的,因此就可以直接在命令行中运行supervisor script.js命令了。
Node.js 支持命令行下的单步调试。下面是一个简单的程序: