Node.js 开发-基础学习

匿名 (未验证) 提交于 2019-12-03 00:22:01

Node.js 使用单线程、事件驱动来实现高并发。这些特性不仅带来巨大的性能提升,还减少了多线程程序设计的复杂性,进而提高了开发效率。

事件驱动(通过回掉函数实现单线程事件驱动)的缺点:不符合开发者的常规线性思路,往往需要把一个完整的逻辑拆分为一个个事件,增加了开发难度和调试难度。

Node.js 最大的特点就是异步式 I/O(或者非阻塞 I/O)与事件紧密结合的编程模式。这种模式与传统的同步式 I/O 线性的编程思路有很大的不同,因为控制流很大程度上要靠事件和回调函数来组织,一个逻辑要拆分为若干个单元。

什么是阻塞(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 使用了单线程、非阻塞的事件编程模式。

Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。在开发者看来,事件由 EventEmitter 对象提供。前面提到的 fs.readFile 和 http.createServer 的回调函数都是通过 EventEmitter 来实现的。

// event.js var EventEmitter = require('events').EventEmitter; var event = new EventEmitter();  event.on('some_event', function() {     console.log('some_event occured'); });  setTimeout(function(){     event.emit('some_event'); }, 2000);

运行这段代码,1秒后控制台输出了 some_event occured.。其原理是 event 对象
注册了事件 some_event 的一个监听器,然后我们通过 setTimeout 在1000毫秒以后向 event 对象发送事件 some_event,此时会调用 some_event 的监听器。

模块是 Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个Node.js 文件就是一个模块,这个文件可能是 JavaScript 代码、JSON 或者编译过的 C/C++ 扩展。
在前面章节的例子中,我们曾经用到了 var http = require(‘http’),其中 http 是 Node.js 的一个核心模块,其内部是用 C++ 实现的,外部用 JavaScript 封装。我们通过require 函数获取了这个模块,然后才能使用其中的对象。

在 Node.js 中,创建一个模块非常简单,因为一个文件就是一个模块,我们要关注的问题仅仅在于如何在其他文件中获取这个模块。Node.js 提供了 exports 和 require 两个对象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。

//module.js var name; exports.setName = function(thyName) {     name = thyName; }; exports.sayHello = function() {     console.log('Hello ' + name); }; // 在同一目录下创建 getmodule.js,内容是: //getmodule.js var myModule = require('./module'); myModule.setName('BYVoid'); myModule.sayHello();

上面这个例子有点类似于创建一个对象,但实际上和对象又有本质的区别,因为
require 不会重复加载模块,也就是说无论调用多少次 require,获得的模块都是同一个。我们在 getmodule.js 的基础上稍作修改:

// loadmodule.js var hello1 = require('./module'); hello1.setName('ByVoid');  var hello2 = require('./module'); hello2.setName('ByVoid 2'); hello1.sayHello();

运行后发现输出结果是 Hello BYVoid 2,这是因为变量 hello1 和 hello2 指向的是
同一个实例,因此 hello1.setName 的结果被 hello2.setName 覆盖,最终输出结果是由后者决定的。

目标:有时候,我们只是想把一个对象封装到模块中,例如

function Hello() {     var name;     this.setName = function(thyname) {         name = thyname;     }     this.sayHello = function() {         console.log('Hello ' + name);     } }  exports.Hello = Hello;

此时我们在其他文件中需要通过 require(‘./singleobject’).Hello 来获取 Hello 对象,这略显冗余,可以用下面方法稍微简化:

function Hello() {     var name;     this.setName = function(thyName) {         name = thyName;     };     this.sayHello = function() {         console.log('Hello ' + name);     }; }; module.exports = Hello;

注意,模块接口的唯一变化是使用 module.exports = Hello 代替了 exports.Hello=
Hello。在外部引用该模块时,其接口对象就是要输出的 Hello 对象本身,而不是原先的 exports。

事实上,exports 本身仅仅是一个普通的空对象,即 {},它专门用来声明接口,本
质上是通过它为模块闭包的内部建立了一个有限的访问接口。因为它没有任何特殊的地方,所以可以用其他东西来代替,譬如我们上面例子中的 Hello 对象。

注意:不可以通过对 exports 直接赋值代替对 module.exports 赋值。exports 实际上只是一个和 module.exports 指向同一个对象的变量,它本身会在模块执行结束后释放,但 module 不会,因此只能通过指定 module.exports 来改变访问接口。

正如当年为了统一 JavaScript 语言标准,人们制定了 ECMAScript 规范一样,如今为了统一 JavaScript 在浏览器之外的实现,CommonJS 诞生了。CommonJS 试图定义一套普通应用程序使用的API,从而填补 JavaScript 标准库过于简单的不足。CommonJS 的终极目标是制定一个像 C++ 标准库一样的规范,使得基于 CommonJS API 的应用程序可以在不同的环境下运行,就像用 C++ 编写的应用程序可以使用不同的编译器和运行时函数库一样。为了保持中立,CommonJS 不参与标准库实现,其实现交给像 Node.js 之类的项目来完成。

node.js 多环境工具 npm install -g n
n use ${version}

node *.js

node 在在运行 HTTP 服务的时候,如果修改代码不会自动刷新,理由是修改代码不会同步到内存中。如果我们需要调试,需要使用 supervisor 进行辅助调试,supervisor 的功能可以帮助实时监听代码的变化,当代码发生变化的时候,直接重新载入代码。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!