爲何要用Node?html
Node把非阻塞IO做爲提升應用性能的方式。而在JS中,天生擁有着異步編程機制: 事件機制。同時JS中不存在多進程。這樣當你執行相對較慢須要花費時間長的IO操做時並不會阻塞主進程的任務。編程
在NodeJS中流行兩種響應邏輯的管理方式: 回調, 事件監聽。json
回調一般用來定義一次性響應的邏輯。事件監聽器本質上也是一個回調,不一樣的是它跟事件相互關聯。數組
回調是一個函數,被當作參數傳遞給異步函數,描述了異步操做完成後要作什麼。服務器
案例: 建立一個簡單的http服務器實現以下功能異步
1. 異步獲取存放在JSON文件中的文章標題socket
2. 異步獲取簡單的HTML模版異步編程
3. 將文章標題組裝到HTML頁面中函數
4. 將HTML頁面發送給用戶性能
var http = require("http"); var fs = require("fs"); var srcFilename = "./titles.json"; var distFilename = "./index.html"; http.createServer(function(req, res){ if(req.url == '/'){ fs.readFile(srcFilename, function(err, data){ if(err){ console.log(err); res.end("server end"); }else{ var titles = JSON.parse(data); fs.readFile(distFilename, "utf-8", function(err, data){ if(err){ console.log(err); res.end("server end"); }else{ var html = data.replace("%", titles.join("</li><li>")); res.writeHead(200, {"Content-type":"text/html"}); res.write(html); res.end(); } }); } }); } }).listen(8080, "127.0.0.1");
以上是NodeJS主程序,經過http模塊建立一個簡單的HTTP服務器。監聽指定端口8080和127.0.0.1主機地址。另外經過判斷request.url請求路徑來使用fs讀取不一樣文件中的內容,最後將這些內容填充到相應文本內容中,經過response響應對象將數據發送給用戶。json數據源和html模板文件以下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta id="viewport" name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no"> <title>Node課程</title> </head> <body> <h1>最新Node課程內容:</h1> <ul> <li>%</li> </ul> </body> </html>
[ "NodeJS模塊原理講解", "NodeJS經常使用模塊簡單講解", "NodeJS異步編程基礎" ]
經過給事件綁定一個觸發時的回調函數,那麼當事件發射器觸發事件時,會在事件觸發時執行這些回調函數。NodeJS中不少內置核心組件都是事件發射器的子類。如HTTP服務器,Net服務器和Stream流對象等。
簡單案例
var net = require("net"); var server = net.createServer(function(socket){ socket.on("data", function(data){ socket.write("輸入的內容是:"); socket.write(data); }); }); server.listen(8888, "127.0.0.1");
監聽器能夠針對某些特殊事件的監聽只調用一次事件處理的回調函數。使用 .once() 方式
接下來咱們就來建立一個屬於本身的事件發射器。
首先咱們先回顧一下events核心模塊 具體能夠參考這篇文章: Events核心模塊講解
接下來建立一個簡單的發佈訂閱系統,實現如下功能:
1. 用戶鏈接服務後,能夠看到當前正處於鏈接狀態的其餘用戶
2. 用戶鏈接服務後,能夠給全部用戶發送消息
3. 用戶斷開服務後,系統會將該用戶從鏈接用戶池中移除
4. 處於某種緣由須要暫停服務時,能夠經過指定的命令中止服務
var events = require("events"); var net = require("net"); // 建立一個頻道發射器,用來管理全部的用戶,用戶的行爲事件及其響應 var channel = new events.EventEmitter(); channel.clients = {}; channel.subscriptions = {}; channel.setMaxListeners(50); // 註冊用戶鏈接服務的事件 channel.on('join', function(id, client) { channel.clients[id] = client; channel.subscriptions[id] = function(senderId, message){ if(id != senderId){ channel.clients[id].write(message); } }; channel.on('broadcast', channel.subscriptions[id]); // 鏈接服務後先友好地提示當前房間內的人數 var welcome = "Welcome! Guests online: " + this.listeners("broadcast").length; client.write(welcome); }); // 註冊用戶斷開服務的事件 channel.on('leave', function(id) { // 移除該用戶的廣播消息事件響應 channel.removeListener("broadcast", channel.subscriptions[id]); // 將消息廣播給其餘用戶 channel.emit("broadcast", id, id + " has left the chat.\n"); }); // 註冊暫停服務的事件 channel.on('shutdown', function() { // 先發消息提醒全部用戶,服務已經暫停 channel.emit("broadcast", '', 'Chat has shut down.\n'); // 移除全部的廣播事件 channel.removeAllListener("broadcast"); }); net.createServer(function(socket){ var id = socket.remoteAddress + " : " + socket.remotePort; // 觸發用戶鏈接服務的事件 channel.emit("join", id, socket); // 註冊用戶發送消息的事件 socket.on('data', function(data){ var data = data.toString(); // 這裏先簡單設置爲暫停服務的指令爲shutdown if(data == "shutdown"){ channel.emit("shutdown"); }else{ channel.emit("broadcast", id, data.toString()); } }); // 註冊用戶離開服務的事件 socket.on('close', function() { channel.emit("leave", id); }); }).listen(8080, '127.0.0.1');
** 看了前面的兩個事件發射器案例,你還能夠利用事件發射器來建立一個文件監聽器。
一般的作法都是建立一個JS類,經過繼承EventEmitter類來處理文件目錄下的全部文件。經過監視目錄中的文件變化從而將變化的文件進行處理。
var events = require("events"); var util = require("util"); var fs = require("fs"); function Watcher(watcherDir, processedDir){ this.watcherDir = watcherDir; this.processedDir = processedDir; } util.inherit(Watcher, events.EventEmitter); Watcher.prototype.watch = function(){ var watcher = this; fs.readdir(watcher.watcherDir, function(err, files){ if(err){ throw err; }else{ for(var index in files){ if(files[index].isFile()){ watcher.emit("process", files[index]); } } } }); }; Watcher.prototype.start = function(){ var watcher = this; fs.watchFile(watcherDir, function(){ watcher.watch(); }); }; // 建立這樣一個文件監聽器實例 var watchDir = "./watch"; var processedDir = "./done"; var watcher = new Watcher(watcherDir, processedDir); // 註冊文件處理函數 watcher.on('process', function(file) { var watcherFile = this.watcherDir + "/" + file; var processedFile = this.processedDir + "/" + file.toLowerCase(); // 經過重命名的方式來移動文件 fs.rename(watchFile, processedFile, function(err){ if(err){ throw err; } }); }); watcher.start();
異步編程的代碼,回調越多,格式化的代碼形狀看起來就像格鬥遊戲中的角色發出的波。很顯然這是你們不肯意看到的。
那麼如何讓異步任務可以順序執行呢?程序流程控制被分爲了兩類: 串行和並行。
串行就是任務一個接着一個的執行,執行完前一個才能執行接下來的一個。
串行化流程控制的本質在於如何將多個異步任務按照預期的順序放入一個數組隊列中。這樣當一個任務執行結束後會從隊列中取出下一個任務依次執行。
並行就是任務不須要一個接着一個來執行,而是能夠交叉執行,使得任務看起來就像是同時在執行同樣。
var fs = require("fs"); var path = require("path"); // 已經完成的任務數 var completedTasks = 0; // 待完成的任務數組 var tasks = []; // 全部單詞的統計結果 var wordCounts = {}; // 讀取的目錄 var filesDir = './text'; function checkIfComplete(){ completedTasks++; if(completedTasks == tasks.length){ for(var word in wordCounts){ console.log("word = " + word + " ; count = " + wordCounts[word]); } } } function countWordsInText(text){ var words = text.toString().toLowerCase().split(/\W+/);//.sort(); console.log(words); for(var i in words){ var word = words[i]; if(word){ wordCounts[word] = (wordCounts[word])? (wordCounts[word] + 1) : 1; } } } fs.readdir(filesDir, function(err, fileList){ if(err){ throw err; }else{ for(var index in fileList){ console.log(path.join(filesDir, fileList[index])); var task = (function(file){ return function(){ fs.readFile(file, function(err, text){ if(err){ throw err; }else{ countWordsInText(text); checkIfComplete(); } }); }; })(path.join(filesDir, fileList[index])); tasks.push(task); } for(var i in tasks){ tasks[i](); } } });