node.js开发指南读书笔记(1)

3.1 开始使用Node.js编程

3.1.1 Hello World

将以下源代码保存到helloworld.js文件中

console.log(‘Hello World!‘);
console.log(‘%s:%d‘, ‘hello‘, 25);

找到文件位置,执行node helloworld.js。结果如下:

3.1.2 Node.js命令行工具

  输入:node --help可以看到详细的帮助信息。

  

  除了直接运行脚本外,node --help显示的用法中说明了另一种输出hello world方式。

  

  使用node的REPL模式

  REPL(Read-eval-print loop),即输入-求值-输出循环。

  

  进入REPL模式后,会出现一个">"提示符提示你输入,输入后按回车,Node.js将会解析并执行命令。如果你执行了一个函数,那么REPL还会在下面显示这个函数的返回值,上面例子中的undefined就是console.log的返回值,如果你输入了一个错误指令,REPL会立即显示错误并输出调用栈。在任何时候连续按两次CTRL + C即可退出Node.js的REPL模式。

  node提出REPL在应用开发时会给人带来很大的便利,例如我们可以测试一个包能否正常使用,单独调用应用的某一个模块,执行简单的计算等。

3.1.3 建立HTTP服务器

  node.js的服务器架构与PHP架构

  

  建立一个名为app.js的文件,代码如下:

var http = require(‘http‘);

http.createServer(function(req, res){
    res.writeHead(200, {‘Content-type‘:‘text/html‘});
    res.write(‘<h1>Node.js</h1>‘);
    res.end(‘<p>Hello world</p>‘);
        }).listen(3000);
console.log(‘HTTP server is listening at port 3000.‘)

  运行node app.js,打开浏览器http://127.0.0.1:3000,就可以看到图3-2所示的内容。

  

          图3-2 用Node.js实现的Http服务器

  用Node.js实现的最简单的HTTP服务器就这样诞生了。这个程序调用了Node.js提供的http模块,对所有HTTP请求答复同样的内容监听3000端口。在终端中运行这个脚本时,我们会发现它并不像Hello World一样结束后立即退出,而是一直等待,直到按下CTRL + C才会结束。这是因为listen函数中创建了事件监听器,使得Node.js进程不会退出事件循环。

  小技巧——使用supervisor。

  安装:$ npm install -g supervisor

  如果你使用的是linux或Mac,直接键入上面的命令很可能有权限错误。原因是npm需要把supervisor安装到系统目录,需要管理员权限,可以使用sudo npm install -g supervisor命令来安装。

  接下来使用supervisor命令启动app.js

  

3.2 异步式I/O与事件式编程

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

3.2.1 阻塞与线程

  什么是阻塞(block)呢?线程在执行中如果遇到磁盘读写或者网络通信(统称为I/O操作),通常需要耗费较长的时间,这时操作系统会剥夺这个线程的CPU控制权,使其暂停执行,同时将资源让给其他的工作线程,这个线程调度方式称为阻塞。当I/O操作完毕时,操作系统将这个线程的阻塞状态解除,恢复其对CPU的控制权,令其继续执行。这种I/O模式就是通常的同步式I/O(Synchronous 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使用了单线程、非阻塞的事件编程模式。

  图3-3和图3-4分别是多线程同步式与单线程异步式I/O的示例。假设我们有一项工作,可以分为两个计算部分和一个I/O部分,I/O部分占的时间比计算多得多(通常都是这样的)。如果我们使用阻塞I/O,那么要想获得高并发就必须开启多个线程。而使用异步式I/O时,单线程即可胜任。

  单线程事件驱动的异步式I/O比传统多线程阻塞式I/O究竟好在哪里呢?简而言之,异步式I/O就是少了多线程的开销。对操作系统来说,创建一个线程的代价是十分昂贵的,需要给它分配内存,列入调度,同时在线程键切换的时候,还要执行内存换页,CPU的缓存被清空,切换回来的时候,还要重新从内存中读取信息,破坏了数据的局部性。

  当然,异步式编程的缺点在于不符合人们一般的程序设计思维,容易让控制流变得晦涩难懂,给编码和调试都带来不小的困难。

  

3.2.2 回调函数

  让我们看看在Node.js中如何使用异步的方式读取一个文件,下面是一个例子:

// readfile.js
var fs = require(‘fs‘);
fs.readFile(‘file.txt‘, ‘utf-8‘, function(err, data)
{
    if (err)
    {
        console.error(err);
    } else
    {
        console.log(data);
    }
});
console.log(‘end‘)

  在新建一个文件file.txt,输入hello world!

  运行结果如下:

  D:\001code\0011nodejs>node readfile.js
  end
  hello world!

  Node.js 也提供了同步读取文件的API:

// readfilesync.js
var fs = require(‘fs‘);
var data = fs.readFileSync(‘file.txt‘, ‘utf-8‘);
console.log(data);
console.log(‘end‘);

  运行结果如下:

  D:\001code\0011nodejs>node readfilesync.js
  hello world!

  end

  比较运行结果:同步的先输出读取文件的内容,在输出end,而异步是先输出end,再输出文件的内容。fs.readFile调用时所做的工作只是将异步式I/O请求发送给了操作系统,然后立即返回并执行后面的语句,执行完以后进入事件循环监听事件。当fs收到I/O请求完成的事件时,事件循环会主动调用回调函数以完成后续工作。因此我们会先看到end,再看到file.txt文件的内容。

3.2.3 事件

  Node.js所有的异步I/O操作在完成时都会发送一个事件到事件队列。在开发者看来,事件由EventEmitter对象提供。前面提到的fs.readFile和http.createServer的回调函数都是通过EventEmitter来实现的。下面我们用一个简单的例子说明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‘);
}, 1000);

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

Node.js的时间循环机制
  Node.js在什么时候会进入事件循环呢?答案是Node.js程序由事件循环开始,到事件循环结束,所有的逻辑都是事件的回调函数,所以Node.js始终在事件循环中,程序入口就是事件循环第一个事件的回调函数。事件的回调函数在执行的过程中,可能会发出I/O请求或直接发射(emit)事件,执行完毕后再事件循环,事件循环会检查事件队列中有没有未处理的事件,直到程序结束。

  与其他语言不同的是,Node.js没有显示的事件循环,类似Ruby的EventMachine::run()的函数在Node.js中是不存在的。Node.js的事件循环对开发者不可见,由libev实现。libev支持多种类型的事件,如ev_io, ev_timer, ev_signal,
ev_idle等,在Node.js中均被EventEmitter封装。libev事件循环的每一次迭代,在Node.js中就是一次Tick,libev不断检查是否有活动的、可供检查的时间监听器,直到检测不到时才退出事件循环,进行结束。事件,执行完毕后再事件循环,事件循环会检查事件队列中有没有未处理的事件,直到程序结束。

  与其他语言不同的是,Node.js没有显示的事件循环,类似Ruby的EventMachine::run()的函数在Node.js中是不存在的。Node
.js的事件循环对开发者不可见,由libev实现。libev支持多种类型的事件,如ev_io, ev_timer, ev_signal, ev_idle等,在Node.js中均被EventEmitter封装。libev事件循环的每一次迭代,在Node.js中就是一次Tick,libev不断检查是否有活动的、可供检查的时间监听器,直到检测不到时才退出事件循环,进行结束。

  

3.3 模块和包

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

3.3.2 创建及加载模块
(1) 创建模块
  在Node.js中,创建一个模块非常简单,因为一个文件就是一个模块,我们要关注的问题仅仅在于如何在其他文件中获取这个模块。Node.js提供了exports和require两个对象,其中exports是模块公开的接口,require用于从外部获取一个模块的接口,即所获取模块的exports对象。
  让我们以一个例子来了解模块。创建一个module.js的文件,内容是:

// 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();

  运行 node getmodule.js,结果如下:

  hello BYVoid

(2)单次加载

/*===============================================
# Last modified:2014-08-27 13:55
# Filename:loadmodule.js
# Description: 单次加载示例
=================================================*/

var hello1 = require(‘./module‘);
hello1.setName(‘BYVoid1‘);

var hello2 = require(‘./module‘);
hello2.setName(‘BYVoide2‘);

hello1.sayHello();

  运行 node loadmodule.js 结果如下:

  Hello BYVoide2

  这是因为变量hello1和hello2指向的是同一个实例,因此hello1.setName的结果被hello2.setName覆盖,最终结果是由后者决定。

  (3)覆盖exports

  a 第一种方法 exports.Hello

  模块

/*===============================================
# Author: RollerCoaster
# Last modified:2014-08-27 14:02
# Filename: singleobject.js
# Description: 覆盖exports示例1
=================================================*/
function Hello(){
    var name;
    this.setName = function(thyName){
        name = thyName;
    };

    this.sayHello = function(){
    console.log(‘Hello ‘ + name + ‘!‘);
    };
};

exports.Hello = Hello;

  调用代码:

/*===============================================
# Author: RollerCoaster
# Last modified:2014-08-27 14:05
# Filename: runSingleObject.js
# Description: 调用singleobject模块
=================================================*/
var Hello = require(‘./singleobject‘).Hello;
hello = new Hello();
hello.setName(‘roller coaster‘);
hello.sayHello();

  运行结果:

  Hello roller coaster!

    b 第二种方法 module.exports = Hello;

  模块代码:

/*===============================================
# Author: RollerCoaster
# Last modified:2014-08-27 14:17
# Filename: hello.js
# Description: hello 模块
=================================================*/
function Hello(){
    var name;
    this.setName = function(thyName){
    name=thyName;
    };    

    this.sayHello = function(){
    console.log(‘Hello ‘ + name + ‘!‘);
    }
};

module.exports = Hello;

  调用代码

/*===============================================
# Author: RollerCoaster
# Last modified:2014-08-27 14:17
# Filename: gethello.js
# Description: 调用hello模块
=================================================*/
var Hello = require(‘./Hello‘);
hello = new Hello();
hello.setName(‘roller coaster‘);
hello.sayHello();

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

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

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

时间: 2024-11-06 21:11:38

node.js开发指南读书笔记(1)的相关文章

NODE.JS开发指南学习笔记

1.Node.js是什么 Node.js是一个让JS运行在服务器端的开发平台,它可以作为服务器向用户提供服务.Node.js中的javascript只是Core javascript,或者说是ECMAJavaScript的一个实现.2.Node.js能做什么 JS是为客户端为生,而Node.js是为网络而生.利用它可以轻松的开发出很多网站.社交应用.服务器等等. Node.js内建有一个HTTP服务器支持,可以实现一个网站和服务器的组合.3.异步式I/O和事件驱动 Node.js最大的特点就是采

NODE.JS开发指南学习笔记2

1.核心模块 核心模块是Node.js的心脏,由一些精简高效的库组成,为其提供了基本的API.2.全局对象 global.所有的的全局变量都是其属性.其根本的作用是作为全局变量的宿主.3.全局变量 1.process:描述当前Node.js的进程状态的对象,提供一个与操作系统的简单接口. 方法 process.argv 命令行参数数组 三个参数1.node 2.脚本文件名 3.运行参数 1.process.stdout: 标准输出流,通常使用的console.log()向标准输出打印字符,而pr

电子书 Node.js开发指南.pdf

<图灵原创:Node.js开发指南>首先简要介绍Node.js,然后通过各种示例讲解Node.js的基本特性,再用案例式教学的方式讲述如何用Node.js进行Web开发,接着探讨一些Node.js进阶话题,最后展示如何将一个Node.js应用部署到生产环境中. <图灵原创:Node.js开发指南>面向对Node.js感兴趣,但没有基础的读者,也可供已了解Node.js,并对Web前端/后端开发有一定经验,同时想尝试新技术的开发者参考. 限个人学习使用,不得用于商业用途,请在下载后2

《node.js开发指南》读后感

<node.js开发指南>这部只有180多页的书,我花了一个多月的业余时间算是粗略看完了.中间因为公司项目的加班,中断了几次.大大拖累进度,现在空出来时间,写一点自己的小小感想吧. 先从缺点开始: 我认为最大缺点就是老了.node是一个快速变化的东东,这本书上的内容,在现在的node上出现了很大的分叉.比如,书中提到安装node的时候,使用系统的apt-get或yum工具安装.可是这样安装之后的node.js的终端工具是nodejs,而不是node.这个就导致了一个新的问题,在这本书中的后一节

JavaScript、jQuery、HTML5、Node.js实例大全-读书笔记3

技术非常多,样例非常多.仅仅好慢慢学,慢慢实践!!如今学的这本书是[JavaScript实战----JavaScript.jQuery.HTML5.Node.js实例大全] JavaScript.jQuery.HTML5.Node.js实例大全-读书笔记2 3.3 响应鼠标动作 图3-2的效果已经有了,须要鼠标来操作展示想看的照片.这就须要在对应的地方加上事件. 3.3.1 响应小照片单击动作 在3.2.3的代码里提供了显示小图列表的eg.showThumb()方法.在单击小图片时要显示大图片.

JavaScript、jQuery、HTML5、Node.js实例大全-读书笔记2

技术很多,例子很多,只好慢慢学,慢慢实践!!现在学的这本书是[JavaScript实战----JavaScript.jQuery.HTML5.Node.js实例大全] JavaScript.jQuery.HTML5.Node.js实例大全-读书笔记1 3.2 照片加载与定位 根据功能设计,可以先写好 HTML 结构基础,再配好 CSS 做出大致效果,最后用 JavaScript加上各种动作.首先请看 HTML 代码结构. 3.2.1 HTML 代码 CSS代码保存到 eg3.css 文件中,Ja

【读书笔记】Node.js开发指南

一:Node.js是什么? 正如当年为了统一 JavaScript 语言标准,人们制定了 ECMAScript 规范一样,如今为了统一 JavaScript 在浏览器之外的实现, CommonJS 诞生了. CommonJS 试图定义一套普通应用程序使用的API,从而填补 JavaScript 标准库过于简单的不足. CommonJS 的终极目标是制定一个像 C++ 标准库一样的规范,使得基于 CommonJS API 的应用程序可以在不同的环下运行,就像用 C++ 编写的应用程序可以使用不同的

Node.js 开发指南笔记

第一章:node简介 介绍了node是什么:node.js是一个让javascript运行在服务器端的开发平台, node能做些什么:[书上的] 具有复杂逻辑的网站 基于社交网络的大规模Web应用 Web Socket服务器 TCP/UDP套接字应用程序 命令行工具 交互式终端程序 带有图形用户界面的本地应用程序 单元测试工具 客户端Javascript编译器 node能做些什么:[网上比较好的一段说明]: NodeJS的作者说,他创造NodeJS的目的是为了实现高性能Web服务器,他首先看重的

node.js 开发指南--调试

在开发 Node.js 实现的 HTTP 应用时会发现,无论你修改了代码的哪一部份,都必须终止Node.js 再重新运行才会奏效.这是因为 Node.js 只有在第一次引用到某部份时才会去解析脚本文件,以后都会直接访问内存,避免重复载入,因为我们在开发过程中总是希望修改后立即看到效果,而不是每次都要终止进程并重启. 1.小技巧——使用 supervisor npm install -g supervisor 执行 $ supervisor app.js supervisor 这个小工具可以解决开