首页 > 代码库 > 读书笔记: 深入浅出node.js

读书笔记: 深入浅出node.js


>> 深入浅出node.js

node.js是c++编写的js运行环境

 

浏览器: 渲染引擎 + js引擎

后端的js运行环境

node.js用google v8引擎,同时提供很多系统级的API(文件操作 网络编程...)

node.js采用事件驱动 异步编程,为网络服务而设计

浏览器端的js有各种安全限制

node.js提供的多数API都是基于事件的,异步的风格。

node.js的优点:充分利用系统资源,执行代码不会被阻塞以等待某个操作的完成;这个设计非常适合后端的网络服务编程。通过事件注册,回调,可以提高资源的利用率,改善性能。

为方便服务器开发,node.js的网络模块有很多:
HTTP, HTTPS, DNS, NET, UDP, TLS等,有助于快速构建web服务器。

node.js的特点:事件驱动,异步编程,单进程,单线程。
node.js内部通过单线程高效率地维护事件循环队列,没有太多的资源占用和上下文切换。

单核性能出色的node.js如何利用多核CPU呢?建议运行多个node.js进程,用某种通信协议协调各项任务。

javascript的匿名函数和闭包特性非常适合事件驱动,异步编程。

node.js最擅长的事情是与其他服务通信。因为node.js是基于事件的无阻塞的,所以非常适合处理并发请求;缺点:node.js是相对新的一个开源项目,所以不太稳定,一直的变,而且缺少足够多的第三方库支持。

知名项目托管网站GitHub也尝试了node应用(NodeLoad). 使用node.js的项目还有MyFox

Node库socket.io

>> node.js的安装和配置
window版已经编译好的node.js安装包 nodejs.msi,安装后在Node命令行环境输入 node -v,有版本号输出,则说明安装好了。

NPM(Node Package Manager)NodeJs的包管理器。通过NPM我们可以安装Nodejs的第三方库。

Unix/Linux安装NPM:
curl http://npmjs.org/install.sh | sudo sh
获取shell脚本,交给sh命令以sudo权限执行。
安装完之后 输入 npm 回车,如输出帮助信息 则表明安装成功。

用NPM安装第三方库:如underscore
npm install underscore
由于一些特殊的网络环境用npm安装第三方库时,容易卡死;可以通过一个镜像的npm资源库来安装,如:
npm --registry "http://npm.hacknodejs.com/" install underscore

设置为默认npm资源库:
npm config set registry "http://npm.hacknodejs.com" , 设置之后就可以直接 npm install underscore

window下安装NPM:
下载较新的nodejs.msi(v0.10...)安装包并安装后,npm默认也安装了。

 

命令行下输入 npm回车即可查看npm的帮助信息。

 


--------------------------------------------------------
node.js中的js模块文件和script标签加载的js文件的区别:node.js的模块文件中声明的变量是在闭包内的,不会污染全局环境,需要被外部调用的接口都挂在exports上。

>> node.js的事件机制
node.js的特点:Evented I/O for V8 javascript (基于V8引擎的事件驱动I/O)

Event模块(events.EventEmitter)是一个简单的事件监听器模式的实现。具有方法:
addListener/on, once, removeListener, removeAllListeners, emit...

node.js的事件与前端Dom树上的事件不同, 不存在冒泡和逐层捕获等行为,自然也没有 preventDefault(), stopPropagation(), stopImmediatePropagation()等处理事件传递的方法

事件侦听器模式也是一种事件钩子(hook)机制,利用事件钩子导出内部数据和状态给外部调用者。


var options = {
host: ‘www.google.com‘,
port: 80,
path: ‘/upload‘,
method: ‘POST‘
};

var req = http.request(options, function(res){
console.log(‘STATUS: ‘ + res.statusCode);
console.log(‘HEADERS: ‘ + JSON.stringfy(res.headers));

res.setEncoding(‘utf8‘);
res.on(‘data‘, function(chunk){
console.log(‘BODY:‘ + chunk);
});

});

req.on(‘error‘, function(e){
console.log(‘problem with request: ‘ + e.message);
});

//write data to request body
req.write(‘data\n‘);
req.write(‘data2\n‘);
req.end();

注:如对于一个事件添加超过10个监听器,会得到一条警告,因为设计者认为侦听器太多,可能导致内存泄漏。调用这个语句可以取消10个侦听器的限制: emitter.setMaxListener(0);

若运行时的错误触发了error事件,会交给error侦听器处理,若没有设置error侦听器,则抛出异常,若异常没有被捕获,将会引起退出。

> 如何继承event.EventEmitter类,如node.js中流对象继承EventEmitter类:
//类式继承
function Stream(){
event.EventEmitter.call(this);
}
util.inherits(Stream, event.EventEmitter);

//util.inherits应该是如下这样实现原型链的
util.inherits = function(subClass, superClass){
function F(){}
F.prototype = superClass.prototype;
subClass.prototype = new F;
subClass.prototype.constructor = subClass;
}

> 多事件之间的协作
在大应用中,数据源和web服务器分离是必然的。好处有:相同数据源开发各种丰富的客户端程序,从多个数据源拉取数据,渲染到客户端。
node.js擅长同时并行发起对多个数据源的请求。
并行请求:
api.getUser(‘username‘, function(profile){
// got the profile
});

api.getTimeline(‘username‘, function(timeline){
// got the timeline
});

api.getSkin(‘username‘, function(skin){
// got the skin
});

这里存在一个问题:请求可以并行发出,但是如何控制回调函数的执行顺序?

若改为这样:
api.getUser(‘username‘, function(profile){
api.getTimeline(‘username‘, function(timeline){
api.getSkin(‘username‘, function(skin){
// todo
});
});
});

这将导致请求不能并行发出。

node.js没有原生支持多事件之间协调的方法,需要借助第三方库。如:

var proxy = new EventProxy();
//all方法作用:侦听完事件后,执行回调,并将侦听接收到的参数传入回调中
proxy.all(‘profile‘, ‘timeline‘, ‘skin‘, function(profile, timeline, skin){
//todo
});

api.getUser(‘username‘, function(profile){
proxy.emit(‘profile‘, profile); //触发事件profile,并传入实参profile
});

api.getTimeline(‘username‘, function(timeline){
proxy.emit(‘timeline‘, timeline);
});

api.getSkin(‘username‘, function(skin){
proxy.emit(‘skin‘, skin);
});

解决多事件协作的另一种方案:Jscex(代码可以用同步的思维去写,异步方式执行)
如:
var data = http://www.mamicode.com/$await(Task.whenAll({
profile: api.getUser(‘username‘),
timeline: api.getTimeline(‘username‘),
skin: api.getSkin(‘username‘)
}));

//使用:data.profile, data.timeline, data.skin
// todo

> 利用事件队列解决雪崩问题
雪崩问题:指在缓存失效的情景下,大量的并发访问同时涌入数据库查询,数据库无法承受,进而导致网站整体很慢。

看看加一个状态锁的解决方案:
var status = ‘ready‘;
var select = function(callback){
if(status ===‘ready‘){
status = ‘pending‘; //上锁
db.select(‘SQL‘, function(results){
callback(result);
status = ‘ready‘; //解锁
});
}
}

连续多次调用select方法时,锁定期间有的select会不被处理;所以应该用事件队列的方式来解决。

var proxy = new EventProxy();
var status = ‘ready‘;
var select = function(callback){
proxy.once(‘selected‘, callback);//将所有请求的回调都压入事件队列中
if(status === ‘ready‘){
status = ‘pending‘;
db.select(‘SQL‘, function(result){
proxy.emit(‘selected‘, result);
status = ‘ready‘;
});
}
}

>> node.js的异步I/O实现
同步:程序中的后续任务都需要等待I/O的完成,等待的过程中无法充分利用CPU。
实现I/O并行,充分利用CPU的方式有2种:多线程但进程, 单线程多进程

getFile(‘file_path‘); //耗时m
getFileFromNet(‘url‘); //耗时n
同步I/O的话,需要m+n, 异步I/O的话,需要max(m,n)
异步I/O在分布式的环境中很重要,能明显改善性能。

> 异步I/O与轮询技术
进行非阻塞的I/O操作时,要读取到完整数据,应用程序要多次轮询,才能确保数据读取完成,然后进行下一步。轮询技术的缺点是应用程序主动多次调用,占用较多CPU时间片。

>> node.js的异步I/O模型
fs.open = function(path, flags, mode, callback){
callback = arguments[arguments.length - 1];
if( (typeof callback) !== ‘function‘ ){
callback = noop;
}

mode = modeNum(mode, 438); /* 438 = 0666 */
binding.open(pathModule._makeLong(path), stringToFlags(flags), mode, callback);
}

>> string buffer

var fs = require(‘fs‘);
var rs = fs.createReadStream(‘testdata.md‘);
var data = http://www.mamicode.com/‘‘;
rs.on(‘data‘, function(trunk){
data += trunk; //trunk 是一个buffer对象
});

rs.on(‘end‘, function(){
console.log(data);
});

npm install bufferhelper;
bufferconcate.js:
var http = require(‘http‘);
var BufferHelper = require(‘bufferhelper‘);
http.createServer(function(req, res){
var bufferHelper = new BufferHelper();
req.on(‘data‘, function(chunk){
bufferHelper.concat(chunk);
});

req.on(‘end‘, function(){
var html = bufferHelper.toBuffer().toString();
res.writeHead(200);
res.end(html);
});
});

 

>> connect模块(node.js web框架)
中间件的流式处理

var app = connect();

//middleware
app.use(connect.staticCache());
app.use(connect.static(__diranme + ‘/public‘) );
app.use(connect.cookieParser());
app.use(connect.session());
...
app.use(function(req, res, next){
//中间件
});

app.listen(3001);

connect用use方法注册中间件到中间件队列中。