node.js事件迴圈
node.js單程式,單執行緒的程式
每一個api都支援回撥
所有的事件機制都是設計模式中的
一共是23種設計模式
http://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/observer.html
一個物件發生改變的時候,將自動通知其他物件,其他物件將相應的做出反應。發生改變的物件為觀察目標,被通知的物件為觀察者。一個觀察目標可以對應多個觀察者,而這些觀察者之間沒有任何聯絡,可以根據需要增加觀察者,使得系統更加容易擴充套件,依賴關係為一對多,又被稱為模型-檢視模式
事件驅動程式
web server接收到請求,將其關閉,進行處理,然後接著服務下一個web請求。
當請求完成以後,放回處理佇列,當到達佇列開頭的時候,將其結果返回給使用者
即非阻塞式I/O 事件驅動I/O
會有一個主迴圈來監聽事件,當檢測到事件以後,觸發回撥函式
程式碼
PS C:UsersmingmDesktoptest> node main.js
連線成功
資料接收成功
程式執行完畢
PS C:UsersmingmDesktoptest>
複製程式碼
// 引入一個 events 模組
var events = require(`events`);
// 建立 eventEmitter物件
var eventEmitter = new events.EventEmitter();
// 建立connection事件的處理程式
var connectHandLer = function connected() {
console.log(`連線成功`);
// 觸發 data_received 事件
eventEmitter.emit(`data_received`);
};
// 繫結cinnection事件處理程式
eventEmitter.on(`connection`, connectHandLer);
// 繫結data_received事件,並完成處理程式的書寫
eventEmitter.on(
`data_received`,
function() {
console.log(`資料接收成功`);
}
);
// 觸發 connection 事件
eventEmitter.emit(`connection`);
console.log(`程式執行完畢`);
複製程式碼
程式的執行過程,先完成各種繫結,觸發connection事件以後,尋找繫結的處理程式,為connected(),然後,執行一半,又被觸發,data_received事件。尋找繫結的處理程式。一個匿名函式,執行,事件全部完成,執行最後一句,程式執行完畢。
多使用者執行的情況下,觸發事件以後,若處理程式正被其他使用者佔用,排隊,直到前方全部處理完成以後,接著該使用者使用處理程式進行處理。
EventEmitter
node所有的非同步I/O操作在完成的時候都會傳送到一個事件到達事件佇列。node裡的物件能夠分發事件
產生的事件的物件都是events.EventEmitter的例項
EventEmitter類
events模組提供一個物件,它是對事件的觸發和事件的監聽的封裝
PS C:UsersmingmDesktoptest> node main.js
事件觸發
PS C:UsersmingmDesktoptest>
複製程式碼
過五秒後響應
// event.js檔案
var EventEmitter = require(`events`).EventEmitter;
var event = new EventEmitter(); // 建立一個event物件
event.on(`some_event`, function(){console.log(`事件觸發`);});
setTimeout(function(){event.emit(`some_event`);}, 5000);
複製程式碼
大概解釋一下這段程式碼
前兩句很簡單,後兩句說一下,event的物件註冊了一個事件的監聽器。這個事件的監聽器為一個匿名函式,事件名稱為some_event,當5000秒以後被觸發先物件event傳送一個事件some_event觸發了匿名函式即監聽器,監聽器被執行。
其中EventEmitter的每個事件由一個事件名和若干引數組成,對於一個事件能有若干的監聽器,當事件觸發的時候,監聽器會被依次呼叫,事件引數作為回撥函式的引數進行傳遞,需要注意的是,監聽器會被依次呼叫
error事件
error是一個單獨列出來的事件,一般要為其繫結一個監聽器,因為node如果丟擲error,若沒有監聽器執行,將會直接退出執行,並返回錯誤
Buffer緩衝區
處理TCP或者檔案流的時候,需要用到二進位制的資料,定義了一個Buffer類,該類用於專門存放二進位制資料的緩衝區
Buffer與字元編碼
const buf = Buffer.from(`ming`, `ascii`); // 宣告一個只讀的變數
console.log(buf.toString(`hex`));
console.log(buf.toString(`utf-8`));
複製程式碼
PS C:UsersmingmDesktoptest> node main.js
6d696e67
ming
PS C:UsersmingmDesktoptest>
複製程式碼
建立一個buffer類,並完成讀取寫入
PS C:UsersmingmDesktoptest> node main.js
23456789:;<=>?@ABCDEFGHIJK
23456789:;<=>?@ABCDEFGHIJK
PS C:UsersmingmDesktoptest>
複製程式碼
buf = Buffer.alloc(26);
for(var i = 0; i < 26; i++){
buf[i] = 50 + i;
};
console.log(buf.toString(`ascii`));
console.log(buf.toString(`utf8`));
複製程式碼
將buffer轉換為jsoon
PS C:UsersmingmDesktoptest> node main.js
{"type":"Buffer","data":[1,2,3,4,5]}
PS C:UsersmingmDesktoptest>
複製程式碼
const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]);
const json = JSON.stringify(buf);
console.log(json);
複製程式碼
將JSON轉換為Buffer
一個轉換方法JSON.parse
> JSON.parse(`{"1": 3, "2": [4, 5, 6]}`, function (k, v) {
... console.log(k); // 輸出當前的屬性名,從而得知遍歷順序是從內向外的,
... console.log("----------"); // 最後一個屬性名會是個空字串。
... console.log(v);
... });
1
----------
3
0
----------
4
1
----------
5
2
----------
6
2
----------
[ <3 empty items> ]
----------
{}
undefined
>
複製程式碼
開始轉換
const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]);
const json = JSON.stringify(buf);
console.log(json);
const copy = JSON.parse( // 呼叫JSON.parse函式,將字串轉換為物件,後一個引數為轉換的函式,轉換的時候將會呼叫
json,
(key, value) => { // 此為es6的語法,一個匿名函式
console.log(value, `----`, !!value, `----`, value.type, `-----`, value.data);
return value && value.type === `Buffer` ? Buffer.from(value.data): value; // 只留下最後一個進行轉換,前面的轉換全部被覆蓋
}
)
// 輸出: <Buffer 01 02 03 04 05>
console.log(copy);
複製程式碼
PS C:UsersmingmDesktoptest> node main.js
{"type":"Buffer","data":[1,2,3,4,5]}
Buffer ---- true ---- undefined ----- undefined
1 `----` true `----` undefined `-----` undefined
2 `----` true `----` undefined `-----` undefined
3 `----` true `----` undefined `-----` undefined
4 `----` true `----` undefined `-----` undefined
5 `----` true `----` undefined `-----` undefined
[ 1, 2, 3, 4, 5 ] `----` true `----` undefined `-----` undefined
{ type: `Buffer`, data: [ 1, 2, 3, 4, 5 ] } `----` true `----` `Buffer` `-----` [ 1, 2, 3, 4, 5 ]
<Buffer 01 02 03 04 05>
PS C:UsersmingmDesktoptest>
複製程式碼
Buffer的合併
var buffer1 = Buffer.from(`222`);
var buffer2 = Buffer.from(`3333`);
var buffer3 = Buffer.concat([buffer1, buffer2]);
console.log(buffer3.toString());
複製程式碼
Stream流
流為一個抽象的介面,
從流中讀取資料
PS C:UsersmingmDesktoptest> node main.js
end!
33333333333333333333333333
PS C:UsersmingmDesktoptest>
複製程式碼
var fs = require(`fs`);
var data = ``;
// 建立可讀流
var readerStream = fs.createReadStream(`input.txt`);
// 設定編碼為 utf8
readerStream.setEncoding(`UTF8`);
// 處理流事件 data事件
readerStream.on(`data`, (chunk) => {data += chunk;}); // 遇到資料讀取,將讀取到的內容賦值給data
// 處理流事件 end事件
readerStream.on(`end`, () => {console.log(data);}); // 將讀取到的儲存到記憶體中的資料列印出來
// 處理事件 error
readerStream.on(`error`, (err) => {console.log(err.stack);}); // 處理error事件,將錯誤輸出,避免程式的執行中斷
console.log(`end!`);
複製程式碼
寫入流
PS C:UsersmingmDesktoptest> node main.js
程式執行完畢
寫入完成
PS C:UsersmingmDesktoptest>
複製程式碼
var fs = require(`fs`);
var data = `這是一段示例`;
// 建立一個可以寫入的流,寫入到檔案output.txt中
var writerStream = fs.createWriteStream(`output.txt`);
// 使用 utf8 編碼寫入資料
writerStream.write(data, `UTF8`);
// 標記檔案末尾
writerStream.end();
// 處理流事件 --> data, end, add error
writerStream.on(`finish`, () => {console.log(`寫入完成`);});
writerStream.on(`error`, () => {console.log(err.stack);});
console.log(`程式執行完畢`);
複製程式碼
管道流
把兩個檔案之間建立流,讓一個檔案的資料流向另外一個檔案
PS C:UsersmingmDesktoptest> node main.js
程式執行完畢
PS C:UsersmingmDesktoptest>
複製程式碼
var fs = require(`fs`);
// 建立一個可讀流
var readerStream = fs.createReadStream(`input.txt`);
// 建立一個可寫流
var writerStream = fs.createWriteStream(`output.txt`);
// 管道讀寫操作
// 將兩個流通過管道連線起來
readerStream.pipe(writerStream);
console.log(`程式執行完畢`);
複製程式碼
這裡需要注意的是,由於流的影響,導致在操作的時候,會覆蓋掉要寫入檔案的內容,原先的內容會被覆蓋
鏈式流
PS C:UsersmingmDesktoptest> node main.js
檔案壓縮完成
PS C:UsersmingmDesktoptest>
複製程式碼
var fs = require(`fs`);
var zlib = require(`zlib`);
// 壓縮 input.txt檔案為 input.txt.gz
fs.createReadStream(`input.txt`) // 建立一個可讀流
.pipe(zlib.createGzip()) // 將建立的可寫流和壓縮流連線
.pipe(fs.createWriteStream(`input.txt.gz`)); // 在建立可寫流,將三個流連線到一起
console.log(`檔案壓縮完成`);
複製程式碼
內容未被覆蓋的寫入
var fs = require(`fs`);
var read = fs.createReadStream(`output.txt`);
var write = fs.createWriteStream(`input.txt`);
read.pipe(write);
console.log(`執行完畢`);
複製程式碼
具體詳細,請查文件
https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options
https://nodejs.org/api/fs.html#fs_file_system_flags
文件寫的很詳細,後面的引數為一個物件,通過物件即可進行修改
模組
模組之前已經闡述過一部分,這裡闡述伺服器端的模組
伺服器端的模組
node中自帶了一個http模組,在程式碼中請求他,並將返回值賦值給本地變數。即本地變數變成了一個擁有所有http模組所提供的公共方法的物件。
node中有四個模組,(原生模組和三種檔案模組)
從檔案模組的快取中新增
原生模組和檔案模組優先順序不同,都會優先從檔案模組的快取中載入以及存在的模組
從原生模組載入
原生模組的優先順序低於檔案模組快取的優先順序。原生模組也有快取區
從檔案載入
這個上一篇以及闡述完成,不在闡述