讀書筆記: 深入淺出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安裝第三方庫:如underscorenpm 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:panlm目錄下,在命令行切換到該目錄,執行 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_modules1. 一個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 bodyreq.write("data
");req.write("data2
");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"); //耗時mgetFileFromNet("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 = 0 */ 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();

//middlewareapp.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方法註冊中間件到中間件隊列中。

你的讚賞是我堅持原創的動力

讚賞共 0 人讚賞
推薦閱讀:

前15筆記
認知世界筆記 12 美麗的太空
學習六爻筆記3
風水筆記(十九)

TAG:筆記 | 讀書筆記 | 讀書 |