讀書筆記: 深刻淺出node.js


>> 深刻淺出node.jsjavascript

node.js是c++編寫的js運行環境html

 

瀏覽器: 渲染引擎 + js引擎前端

後端的js運行環境java

node.js用google v8引擎,同時提供不少系統級的API(文件操做 網絡編程...)node

node.js採用事件驅動 異步編程,爲網絡服務而設計c++

瀏覽器端的js有各類安全限制git

node.js提供的多數API都是基於事件的,異步的風格。github

node.js的優勢:充分利用系統資源,執行代碼不會被阻塞以等待某個操做的完成;這個設計很是適合後端的網絡服務編程。經過事件註冊,回調,能夠提升資源的利用率,改善性能。web

爲方便服務器開發,node.js的網絡模塊有不少:
HTTP, HTTPS, DNS, NET, UDP, TLS等,有助於快速構建web服務器。shell

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的幫助信息。

 

npm install underscore

>> node.js的模塊機制
CommonJs規範:構建javascript在包括服務器,桌面,命令行的生態系統。
node.js實現了用require方法引入其餘模塊,同時npm也基於CommonJs規範定義了包規範,實現了依賴管理和包的自動安裝。

> 模塊的定義和使用
circle.js:
var PI = Math.PI;
exports.area = function(r){
return PI*r*r
}

exports.length = function(r){
return 2*PI*r
}

app.js:
var circle = require('./circle.js');
console.log('the area of circle of radius 4 is ' + circle.area(4) );

> 模塊的載入策略
node.js的模塊分爲2類:原生(核心)模塊 和 文件模塊
原生模塊在node.js源代碼編譯時就編譯進了二進制執行文件,加載速度最快;文件模塊是動態加載的,相對慢。

node.js對require後的原生模塊和文件模塊都有緩存,第二次require時,從緩存得到,不會有重複的開銷。

node.js加載文件模塊的工做是有原生模塊module來實現和完成的。
文件模塊按後綴又分爲3類:
.js 經過fs模塊同步讀取js文件並執行
.node c/c++編寫的addon, 經過dlopen方法加載
.json 讀取文件 用JSON.parse方法解析。

node.js對app.js這類文件模塊的加載過程:
1. 對文件內容進行頭尾包裝,變爲模塊定義的形式。如包裝後變爲:
(function(exports, require, module, __filename, __dirname){
var circle = require('./circle.js');
console.log('the area of circle of radius 4 is ' + circle.area(4) );
})
包裝後用vm模塊的runInThisContext方法執行這個function對象,並傳入對應的實參。

require('./circle.js'); //在這裏circle.js模塊經歷 載入--編譯--緩存的過程,最後返回module對象exports。

> require方法的文件查找策略
node.js有原生模塊和3種文件模塊
先在文件模塊緩存區查找--若沒有-->看看是否原生模塊(是,則到原生模塊緩存區查找,沒找到則加載之並緩存)--若非原生模塊--則查找文件模塊----根據後綴 載入文件模塊並緩存----最後返回exports

require(..)方法的參數:
1. http, fs, path等原生模塊
2. ./mod或../mod,相對路徑的文件模塊
3. /pathtomodule/mod 絕對路徑的文件模塊
4. mod 非原生模塊的文件模塊

module path這個概念:
每個被加載的文件模塊,在建立模塊對象時,這個模塊便會有一個paths屬性,其值是根據當前路徑計算獲得的路徑數組。如:
modulepaths.js:
console.log(module.paths);
把modulepaths.js放到 f:\pan\lm\目錄下,在命令行切換到該目錄,執行 node modulepaths.js,輸出:
[ 'F:\\pan\\lm\\node_modules',
'F:\\pan\\node_modules',
'F:\\node_modules' ]
查找路徑生成規則很明顯,此外還有一個全局的module path: 當前node執行文件的相對目錄 和 環境變量中設置的HOME ,NODE_PATH

簡而言之,若require絕對路徑的文件,查找時不會遍歷每個node_modules目錄,速度最快。

對module path數組的每條路徑都執行某個查找過程。

>> 包結構
一個符合CommonJs規範的包結構應該以下:~~node_modules
1. 一個package.json文件應該存在於包的頂級目錄下。
2. 二進制文件應包含在bin目錄下
3. javascript代碼也應該在bin目錄下
4. 文檔應該在doc目錄下
5. 單元測試應該在test目錄下

module paths路徑數組中,在每條路徑的查找過程當中,在嘗試添加擴展名也沒找到目標文件時,會嘗試將當期路徑當作包來加載,從package.json中獲取信息。讀取package.json的main字段。

package.json的字段:
1. name 包名 須要在npm上是惟一的,不能包含空格。
2. description 包簡介
3. version 版本號 x.y.z 用於版本控制的場景
4. keywords 關鍵字數組,用於npm分類搜素
5. maintainers 包維護者的數組 數組元素是形如 {name:..,email:.., web:..}的對象
6. contributors 包貢獻者的數組,第一個元素是包做者,形如{name:..,email:..}
7. bugs 一個能夠提交bug的url地址
8. license 包所使用的許可證數組 數組元素如{type:...,url:..}
9. repositories 託管源代碼的地址數組
10. dependencies 當前包須要的依賴 這個屬性很重要,NPM會經過它自動加載依賴包。

包符合CommonJs規範後,就能夠發佈,輸入命令
npm publish <folder>
(須要先註冊一個npm帳號 : npm adduser)

若用戶要使用npm上的包,能夠執行:
npm install <package>

本地方式安裝包:
從github手動下載包,在命令行下轉到包的目錄下 執行:
npm install <package.json>

 


--------------------------------------------------------
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 = $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 = '';
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方法註冊中間件到中間件隊列中。

相關文章
相關標籤/搜索