本文的主要內容是對nodejs提供的一些重要模組,結合官方API進行介紹,遇到精彩的文章,我會附在文中並標明瞭出處。主要包括如下8個模組
- buffer 模組
- dns 模組
- process 模組
- child_process 模組
- domain 模組
- cluster 模組
- event 模組
- util 模組
轉載請註明出處,多謝支援~
buffer
關於中文亂碼
在node.js中,一個字串的長度與根據該字串所建立的快取區的長度並不相同,因為在計算字串的長度時,是以文字作為一個單位,而在計算快取區的長度時,是以位元組作為一個單位。
比如針對 ”我喜愛程式設計”這個字串,該字串物件的length屬性值與根據該字串建立的buffer物件的length屬性值並不相同。因為字串物件的length屬性值獲取的是文字個數,而buffer物件的length屬性值獲取的是快取區的長度,即快取區中的位元組。
var str = '勇士隊加油';
console.log(str.length);//5
var buf = new Buffer(str);
console.log(buf.length);//15
複製程式碼
另外,可以使用0開始的序號來取出字串物件或快取區中的資料。但是,在獲取資料時,字串物件是以文字作為一個單位,而快取區物件是以位元組作為一個單位。比如,針對一個引用了字串物件的str變數來說,str2獲取的是第三個文字,而針對一個引用了快取區物件的buf物件來說,buf2獲取的是快取區中的第三個位元組資料轉換為整數後的數值。如下:
console.log(str[2]);//隊
console.log(buf[2]);//135
複製程式碼
正確讀取檔案內容的方式
從上文中可以看出,如果讀取檔案內容是,恰好不是一個完整文字時,可能會輸出錯誤資訊
var fs = require('fs');
var rs = fs.createReadStream('testdata.md', {bufferSize: 11});
var data = '';
rs.on("data", function (trunk){
data += trunk;
});
rs.on("end", function () {
console.log(data);
});
複製程式碼
可能會輸出如下的內容
事件循���和請求���象構成了Node.js���非同步I/O模型的���個基本���素,這也是典���的消費���生產者場景。
複製程式碼
造成這個問題的根源在於data += trunk
語句裡隱藏的錯誤,在預設的情況下,trunk是一個Buffer物件。這句話的實質是隱藏了toString的變換的:
data = data.toString() + trunk.toString();
複製程式碼
由於漢字不是用一個位元組來儲存的,導致有被截破的漢字的存在,於是出現亂碼。解決這個問題有一個簡單的方案,是設定編碼集:
var rs = fs.createReadStream('testdata.md', {encoding: 'utf-8', bufferSize: 11});
複製程式碼
下面展示一個正確讀取檔案,並連線buffer物件的方法
var buffers = [];
var nread = 0;
readStream.on('data', function (chunk) {
buffers.push(chunk);
nread += chunk.length;
});
readStream.on('end', function () {
var buffer = null;
switch(buffers.length) {
case 0: buffer = new Buffer(0);
break;
case 1: buffer = buffers[0];
break;
default:
buffer = new Buffer(nread);
for (var i = 0, pos = 0, l = buffers.length; i < l; i++) {
var chunk = buffers[i];
// 把chunk複製到buffer物件從pos位置開始的地方
chunk.copy(buffer, pos);
pos += chunk.length;
}
break;
}
});
複製程式碼
buf.copy(targetBuffer,[targetStart],[sourceStart],[sourceEnd]);
在Buffer物件的copy方法中,使用四個引數,第一個引數為必須指定的引數,其餘三個引數均為可選引數。第一個引數用於指定複製的目標Buffer物件。第二個引數用於指定目標Buffer物件中從第幾個位元組開始寫入資料,引數值為一個小於目標的Buffer物件長度的整數值,預設值為0(從開始處寫入資料)。第三個引數用於指定從複製源Buffer物件中獲取資料時的開始位置,預設值為0,即從複製源Buffer物件中的第一個位元組開始獲取資料,第四個引數用於指定從複製源Buffer物件中獲取資料時的結束位置,預設值為複製源Buffer物件的長度,即一直獲取完畢複製源Buffer物件中的所有剩餘資料。
推薦文章
粉絲日誌 - nodejs buffer物件 淺析 nodejs buffer 物件
dns
dns.lookup()
根據域名解析ip地址,設定引數可以返回一個域名對應的多個IP
var dns = require('dns');
dns.lookup('www.baid.com', {all: true} function(err, address, family){
console.log(address)
})
[ { address: '122.10.91.48', family: 4 } ]
複製程式碼
注意:lookup函式會受本地host的影響。如,在host檔案中配置了
127.0.0.1 www.baidu.com
使用dns.lookup()查詢www.baidu.com這個域名時,會返回 127.0.0.1。此時可以考慮使用dns.resolve4()方法來替代解析域名。
dns.lookupService(address, port, callback)
使用dns.lookupService(address, port, callback)方法,該方法依賴getnameinfo底層函式。 callback函式有三個引數(err, hostname, service),service是protocol,為http或https,使用如下所示:
dns.lookupService('127.0.0.1',80,(err,hostname,service)=>{
if(err) console.log(err);
console.log('該IP對應的主機為:'+hostname+' 協議為:'+service);
});
// 該IP對應的主機為:localhost 協議為:http
複製程式碼
推薦文章
process 模組
簡介
process是一個全域性內建物件,可以在程式碼中的任何位置訪問此物件,這個物件代表我們的node.js程式碼宿主的作業系統程式物件。使用process物件可以截獲程式的異常、退出等事件,也可以獲取程式的當前目錄、環境變數、記憶體佔用等資訊,還可以執行程式退出、工作目錄切換等操作。
Process模組提供了訪問正在執行的程式。child_process模組可以建立子程式,並與他們通訊。cluster模組提供了實現共享相同埠的叢集服務能力,允許多個請求同時處理。
process實現了EventEmitter介面,exit方法會在當程式退出的時候執行。因為程式退出之後將不再執行事件迴圈,所有隻有那些沒有回撥函式的程式碼才會被執行。在下面例子中,setTimeout裡面的語句是沒有辦法執行到的。
process.on('exit', function () {
setTimeout(function () {
console.log('This will not run');
}, 100);
console.log('Bye.');
});
複製程式碼
屬性
-
process.pid:當前程式的程式號。
-
process.version:Node的版本,比如v0.10.18。
-
process.platform:當前系統平臺,比如Linux。
-
process.title:預設值為“node”,可以自定義該值。
-
process.argv:當前程式的命令列引數陣列。
console.log("argv: ",process.argv.slice(2)); //node test.js a b c //argv: [ 'a', 'b', 'c' ] 複製程式碼
-
process.env:指向當前shell的環境變數,比如process.env.HOME。
-
process.execPath:執行當前程式的可執行檔案的絕對路徑。
-
process.memoryUsage():node程式記憶體的使用情況,rss代表ram的使用情況,vsize代表總記憶體的使用大小,包括ram和swap;
-
process.heapTotal,process.heapUsed:分別代表v8引擎記憶體分配和正在使用的大小。
-
process.stdout:指向標準輸出。
-
process.stdin:指向標準輸入。
-
process.stderr:指向標準錯誤。
方法
-
process.exit():退出當前程式。
-
process.cwd():返回執行當前指令碼的工作目錄的路徑。_
-
process.chdir():改變工作目錄。
-
process.nextTick():將一個回撥函式放在下次事件迴圈的頂部。 process.nextTick()的例子,指定下次事件迴圈首先執行的任務。
process.nextTick(function () { console.log('Next event loop!'); }); 複製程式碼
上面程式碼可以用setTimeout改寫,但是nextTick回撥的優先順序更高,會被放在事件佇列的最前面,而settimeout是放在最後面.
setTimeout(function () { console.log('Next event loop!'); }, 0) 複製程式碼
事件
-
exit事件
當前程式退出時,會觸發exit事件,可以對該事件指定回撥函式。這一個用來定時檢查模組的狀態的好鉤子(hook)(例如單元測試),當主事件迴圈在執行完’exit’的回撥函式後將不再執行,所以在exit事件中定義的定時器可能不會被加入事件列表.
process.on('exit', function () { fs.writeFileSync('/tmp/myfile', 'This MUST be saved on exit.'); }); 複製程式碼
-
uncaughtException事件
在你接觸node之後,你就會發現那些影響了主事件迴圈的異常會把整個node程式宕掉的。這會是相當嚴重的問題,所以process提供了另外一個有用的事件uncaughtException
來解決這個問題,當前程式丟擲一個沒有被捕捉的意外時,會觸發uncaughtException事件。
process.on('uncaughtException', function (err) {
console.log('Caught exception: ' + err);
});
setTimeout(function () {
console.log('This will still run.');
}, 2000);
// Intentionally cause an exception, but don't catch it.
nonexistentFunc();
console.log('This will not run.');
複製程式碼
我們來看上面的例子,我們註冊了uncaughtException事件來捕捉系統異常。執行到nonexistentFunc()時,因為該函式沒有定義所以會丟擲異常。
Caught exception: ReferenceError: nonexistentFunc is not defined
This will still run.
複製程式碼
再看一個例子
var http = require('http');
var server = http.createServer(function(req,res) {
res.writeHead(200, {});
res.end('response');
badLoggingCall('sent response');
console.log('sent response');
});
process.on('uncaughtException', function(e) {
console.log(e);
});
server.listen(8080);
複製程式碼
在這裡例子中我們建立了一個web伺服器,當處理完請求之後,我們會執行badLoggingCall()方法。因為這個方法不存在,所以會有異常丟擲。但是我們註冊的uncaughtException事件會對異常做出處理,這樣伺服器不會受到影響得以繼續執行。我們會在伺服器端記錄錯誤日誌
[ReferenceError: badLoggingCall is not defined]
複製程式碼
但常規不建議使用該粗略的異常捕獲處理,建議使用 domains
child process
child_process是Node.js的一個十分重要的模組,通過它可以實現建立多程式,以利用單機的多核計算資源。雖然,Nodejs天生是單執行緒單程式的,但是有了child_process模組,可以在程式中直接建立子程式,並使用主程式和子程式之間實現通訊,等到子程式執行結束以後,主程式再用回撥函式讀取子程式的執行結果。
推薦文章
domain
nodejs的尷尬
try catch
無法捕獲非同步中的異常。所以我們能做的只能是
app.get('/index', function (req, res) {
// 業務邏輯
});
process.on('uncaughtException', function (err) {
logger.error(err);
});
複製程式碼
這個時候,雖然我們可以記錄下這個錯誤的日誌,且程式也不會異常退出,但是我們是沒有辦法對發現錯誤的請求友好返回的,只能夠讓它超時返回。
這個時候 domain模組就出現了,它可以捕捉非同步錯誤。而我們為了讓 domain 模組來接管所有的http請求中的異常,所以把它寫成一箇中介軟體是非常方便的。
app.use(function (req, res, next) {
var reqDomain = domain.create();
reqDomain.on('error', function (err) { // 下面丟擲的異常在這裡被捕獲,觸發此事件
console.log('捕獲到錯誤');
res.send(500, err.stack); // 成功給使用者返回了 500
});
reqDomain.run(next);
});
複製程式碼
app.use(function(req,res,next){ .....})
這是一箇中介軟體,用來接收所有http請求,這裡你可以捕獲request
和 response
物件用來做一些過濾,邏輯判斷等等,最後通過 next 來放行本次請求,那麼這個中介軟體就完成了他的一次使命.
然後我們在 process 上將未處理的異常捕捉一下,做到萬無一失.
process.on('uncaughtException', function (err) {
console.error("uncaughtException ERROR");
if (typeof err === 'object') {
if (err.message) {
console.error('ERROR: ' + err.message)
}
if (err.stack) {
console.error(err.stack);
}
} else {
console.error('argument is not an object');
}
});
複製程式碼
然後丟擲錯誤實踐一下,是否能被捕捉
app.get('/err', function (req, res) {
//throw new Error('exception');
setTimeout(function () {
throw new Error('exception'); // 丟擲一個非同步異常
}, 1000);
})
複製程式碼
每次 domian 捕獲到錯誤後,我都在控制檯輸出了一行提示資訊 "捕獲到錯誤" 當前程式並沒有因為異常而掛掉,這就是我們要的效果.
我們之所以想到用 setTimeout 就是想模擬一個非同步的回撥,如果你直接 throw new Error('exception');這樣就不是非同步了,直接會被 process 上的 uncaughtException 來接管.
進階文章
## cluster nodejs最大的特點就是單程式、無阻塞執行,並且是非同步事件驅動的。Nodejs的這些特效能夠很好的解決一些問題,例如在伺服器開發中,併發的請求處理是個大問題,阻塞式的函式會導致資源浪費和時間延遲。通過事件註冊、非同步函式,開發人員可以提高資源的利用率,效能也會改善。既 然Node.js採用單程式、單執行緒模式,那麼在如今多核硬體流行的環境中,單核效能出色的Nodejs如何利用多核CPU呢?
cluster是一個nodejs內建的模組,用於nodejs多核處理。cluster模組,可以幫助我們簡化多程式並行化程式的開發難度,輕鬆構建一個用於負載均衡的叢集。
推薦文章
fork其實就是建立子程式的方法,新建立的程式被認為是子程式,而呼叫fork的程式則是父程式。 子程式和父程式本來是在獨立的記憶體空間中的。但當你使用了fork之後,兩者就處在同一個作用域內了。 但是,記憶體的讀寫,檔案的map,都不會影響對方。也就是說,你建立的程式其實可以相互通訊,並且被master程式 管理。
程式間使用訊息通知來共享資料
多程式使用同一埠不衝突的原因
util 模組
簡介
var util = require("util");
複製程式碼
util.inherits(constructor, superConstructor)
util.inherits(constructor, superConstructor)
是一個實現物件間原型繼承的方法。JavaScript 的物件導向特性是基於原型的繼承,與常見的基於類的不同,JavaScript 沒有提供物件繼承的語言級別特性,而是通過原型鏈複製來實現的。inherits方法可以將父類原型鏈上的方法複製到子類中,實現原型式繼承。
var events = require("events");
//MyStream建構函式,在建構函式將this指向本物件
function MyStream() {
events.EventEmitter.call(this);
}
//複製父物件上所有的方法
util.inherits(MyStream, events.EventEmitter);
//對MyStream類新增原型方法
MyStream.prototype.write = function(data) {
this.emit("data", data);
}
var stream = new MyStream();
//由於MyStream繼承自EventEmitter,所以其例項stream是MyStream類的例項也是EventEmitter類的例項
console.log(stream instanceof events.EventEmitter); // true
console.log(MyStream.super_ === events.EventEmitter); // true
//父類中的方法呼叫
stream.on("data", function(data) {
console.log('Received data: "' + data + '"');
})
//子類中的方法呼叫
stream.write("It works!"); // Received data: "It works!"
複製程式碼
event 模組
此模組是一個核心模組,直接引用
var events = require('events');
複製程式碼
簡介
events模組只提供了一個物件,events.EventEmitter,核心是 事件發射 和 事件監聽 功能。每個事件由一個事件名(用於標識事件),和多個引數組成。事件名:字串,通常表達一定的語義;事件被髮射時,監聽該事件的函式被依次呼叫。
監聽
var events = require("events");
var emitter = new events.EventEmitter();
emitter.on("/click", function () {
console.log("first event");
})
emitter.on("/click", function () {
console.log("second event");
})
emitter.emit("/click");
複製程式碼
注意
- /click是事件名(用於標識事件)
- 可以多個監聽,用於監聽同一個事件,然後依次執行;
- 需要先監聽,後發射;
- 監聽是on,發射是emit
- 把emit發射的事件賦值給變數。如果有監聽該事件的,則變數值為true,如果無監聽該事件,則返回值為false。注意,該變數賦值後不會改變,即
var nn = emitter.emit("/click1");
emitter.on("/click1", function () {
console.log("first event");
})
console.log(nn);// false
複製程式碼
只監聽一次:
EventEmitter.once(事件名, 回撥函式)
,即把上面的on替換為once即可,然後這個只監聽一次就失效;
移除事件
EventEmitter.removeListener(事件名, 回撥函式名)
var events = require("events");
var emitter = new events.EventEmitter();
var first = function () {
console.log("first event");
}
var second = function () {
console.log("second event");
}
emitter.on("/click", first)
emitter.on("/click", second)
emitter.emit("/click");
emitter.removeListener("/click", first);
console.log("————移除完成————");
emitter.emit("/click");
輸出
// first event
// second event
// --移除完成--
// second event
複製程式碼
全部移除
var events = require("events");
var emitter = new events.EventEmitter();
var first = function () {
console.log("first event");
}
var second = function () {
console.log("second event");
}
emitter.on("/click", first)
emitter.on("/click", second)
emitter.emit("/click");
emitter.removeAllListeners("/click");
console.log("————移除完成————");
emitter.emit("/click");
輸出
// first event
// second event
// --移除完成--
複製程式碼
error事件
當遇見異常時會發射error事件,EventEmitter規定,如果沒有監聽其的監聽器,Node.js會把其當成異常,退出程式並列印呼叫棧。因此需要設定監聽其的監聽器,避免遇見錯誤後整個程式崩潰。
var events = require("events");
var emitter = new events.EventEmitter();
var first = function () {
console.log("first event");
}
var error = function (error) {
console.log(error);
}
emitter.on("/click", first)
emitter.on("error", error) //如果沒有這一行程式碼,下面在發射error時會出錯然後退出程式
emitter.emit("/click");
emitter.emit("error", error)
console.log("————移除完成————");
emitter.emit("/click");
輸出
// first event
// [Function]
// --移除完成--
// first event
複製程式碼
例項
任何型別如果繼承了該類就是一個事件觸發體,繼承該類的任何型別都是事件的一個例項(給事件繫結一個函式後,一旦觸發事件,馬上執行事件繫結函式.
下面例子演示通過繼承給物件繫結一個事件,來自一介布衣_events模組的精彩示例
var util = require('util');
var events = require('events');
var Anythin = function (name) {
this.name = name;
}
util.inherits(Anythin, events.EventEmitter);
//建立一隻貓
var cat = new Anythin('黑貓');
//繫結事件
cat.on("activity", function (activity) {
console.log(this.name + activity);
});
//建立一隻老鼠
var mouse = new Anythin('老鼠');
//繫結事件
mouse.on("activity", function (activity) {
console.log(this.name + activity);
});
//建立屋子的主人
var people = new Anythin('主人');
//繫結事件
people.on("activity", function (activity) {
console.log(this.name + activity);
});
//建立主人的孩子
var child = new Anythin('嬰兒');
//繫結事件
child.on("activity", function (activity) {
console.log(this.name + activity);
});
console.log('靜靜的夜晚,主人一家正在酣睡......');
console.log('黑貓緊盯著黑暗的角落.....');
setTimeout(function(){
console.log('黑貓再也堅持不住了......');
cat.emit("activity",'睡著了');
mouse.emit("activity",'爬出洞口');
people.emit("activity",'聞聲而起');
child.emit("activity",'開始哭哭啼啼');
},3000);
複製程式碼
上面的例子就是一個萬能的造物主,可以建立宇宙中的萬事萬物.上面建立的一個事件引發體 "黑貓" 由於晚上'上班' 太辛苦,而偷偷去睡覺,這一事件誘因直接導致囂張的'老鼠'從洞裡出來覓食,由於老鼠覓食動作不當而吵醒做夢的'主人'及正在酣睡的'嬰兒' 造物主製造的各種角色時已天生有個繫結事件的活動 activity ,這個功勞要歸功於:
util.inherits(Anythin, events.EventEmitter);
複製程式碼
util 模組也是node.js 中的核心模組,他構建了一些常用的程式碼, inherits 就是其中之一,讓前面的型別繼承後面的的型別.所以上面的程式碼是 Anythin 型別繼承了 EventEmitter 型別,所以EventEmitter 型別實現的方法屬性可以直接使用(當然包括繫結事件觸發函式的方法,註冊事件的方法 等)