node.js开发指南读书笔记(1)
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
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.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来改变访问接口。
node.js开发指南读书笔记(1)的更多相关文章
- Node.js 开发指南-读书笔记
1. Node.js 使用了单 线程.非阻塞的事件编程模式 Node.js 最大的特点就是采用异步式 I/O 与事件驱动的架构设计.对于高并发的解决方 案,传统的架构是多线程模型,也就是为每个业务逻辑 ...
- NODE.JS开发指南学习笔记
1.Node.js是什么 Node.js是一个让JS运行在服务器端的开发平台,它可以作为服务器向用户提供服务.Node.js中的javascript只是Core javascript,或者说是ECMA ...
- NODE.JS开发指南学习笔记2
1.核心模块 核心模块是Node.js的心脏,由一些精简高效的库组成,为其提供了基本的API.2.全局对象 global.所有的的全局变量都是其属性.其根本的作用是作为全局变量的宿主.3.全局变量 1 ...
- Node.js高级编程读书笔记Outline
Motivation 世俗一把,看看前端的JavaScript究竟能做什么. 顺便检验一下自己的学习能力. Audience 想看偏后台的Java程序员关于前端JavaScript的认识的职业前端工程 ...
- JavaScript、jQuery、HTML5、Node.js实例大全-读书笔记3
技术很多,例子很多,只好慢慢学,慢慢实践!!现在学的这本书是[JavaScript实战----JavaScript.jQuery.HTML5.Node.js实例大全] JavaScript.jQuer ...
- JavaScript、jQuery、HTML5、Node.js实例大全-读书笔记2
技术很多,例子很多,只好慢慢学,慢慢实践!!现在学的这本书是[JavaScript实战----JavaScript.jQuery.HTML5.Node.js实例大全] JavaScript.jQuer ...
- Node.js开发指南中的例子(mysql版)
工作原因需要用到nodejs,于是找到了<node.js开发指南>这本书来看看,作者BYVoid 为清华大学计算机系的高材生,年纪竟比我还小一两岁,中华地广物博真是人才辈出,佩服. 言归正 ...
- 学习Nodejs:《Node.js开发指南》微博项目express2迁移至express4过程中填的坑
<Node.js开发指南>项目地址https://github.com/BYVoid/microblog好不容易找到的基础版教程,但书中是基于express2的,而现在用的是express ...
- 《node.js开发指南》partial is not defined的解决方案
由于ejs的升级,<node.js开发指南>中使用的 partial 函数已经摒弃,使用foreach,include代替 原来的代码是: <%- partial('listitem ...
随机推荐
- Flask的请求与响应
Flask的请求与响应 1 请求相关信息 request.method # 请求方法 request.args # get 请求的参数 request.form # post请求的参数 request ...
- 如何使用google搜索
作者:崔凯链接:https://www.zhihu.com/question/20161362/answer/14180620来源:知乎著作权归作者所有,转载请联系作者获得授权. 搜索引擎命令大全! ...
- SITP & Raspberry Pi
系统安装 系统选择树莓派论坛提供的下载地址 Download 我选择了其中的SSH-2017-01-11-raspbian-jessie.zip[良心推荐] 选择一个大于8G的内存卡,利用 Win32 ...
- Android UI之LinearLayout详解
※※※摘自http://www.cnblogs.com/salam/archive/2010/10/20/1856793.html LinearLayout是线性布局控件,它包含的子控件将以横向或竖向 ...
- Yet another A + B
time limit per test 0.25 s memory limit per test 64 MB input standard input output standard output Y ...
- [Luogu3769][CH弱省胡策R2]TATT
luogu 题意 其实就是四维偏序. sol 第一维排序,然后就只需要写个\(3D-tree\)了. 据说\(kD-tree\)的单次查询复杂度是\(O(n^{1-\frac{1}{k}})\).所以 ...
- 区分/不区分大小写的比较,查找字符串在另一字符串中的位置,字符串开头是否包括另一字符串 hasPrefix
NSString *str; // 使用stringWithFormat生成一格式化字符串 str = [NSString stringWithFormat:@"This is %@&quo ...
- LOJ2823 「BalticOI 2014 Day 1」三个朋友
题意 给定一个字符串 S,先将字符串 S 复制一次(变成双倍快乐),得到字符串 T,然后在 T 中插入一个字符,得到字符串 U. 给出字符串 U,重新构造出字符串 S. 所有字符串只包含大写英文字母. ...
- LA3218 Find the Border
题意 PDF 分析 虽然只找外轮廓,但是时间复杂度不比PSLG优秀,所以可以当做联系PSLG的题做. PSLG框架 找出所有交点 交点按序连边 把边按极角序排序 逆时针找圈 然后何以会顺时针找出无限区 ...
- Hadoop(二)自定义输出
Hadoop提供了较为丰富的数据输入输出格式,可以满足很多的设计实现,但是在某些时候需要自定义输入输出格式. 数据的输入格式用于描述MapReduce作业的数据输入规范,MapReduce框架依靠 数 ...