為什么向后端工程師推薦Node.js

其中變量對象VO,包含了所有局部變量的引用。對于main函數,局部變量”id”存儲在VO.id內。看起來用VO來代替我們的 currentSate最合適了。但main函數還可能嵌套在其他函數之內,所以我們需要ScopeChain,它是一個包含當前運行函數VO和其所有父 函數scope的數組。
所以在這個例子中,在onDataLoad函數定義時,就為默認為其綁定了一個[[scope]]屬性指向其父函數的 ExecutionContext的ScopeChain。而當函數onDataLoad執行時,就可以通過[[scope]]屬性來訪問父函數的VO對 象來找到id,如果父函數的VO中沒有id這個屬性,就再繼續向上查找其祖先的VO對象,直到找到id這個屬性或到達最外層返回undefined。也正 是因為這個引用,造成VO的引用計數不為0,在走出作用域時,才不會被垃圾回收。
很多朋友覺得閉包較難理解,其實我們只要能明確的區分函數定義和函數運行兩個時機,那么閉包就是讓函數在運行時能夠訪問到函數定義時的所處作用域內的所有變量,或者說函數定義時能訪問到什么變量,那么在函數運行時通過相同的變量名一樣能訪問到。
關于狀態保持是本文的重點,在我看到的多數Node.js的介紹文章中并沒有詳解這里,我們只是知道了要解決阻塞問題,但是JavaScript解決阻塞問題的優勢到底在哪里,作為一名前端工程師,我想有必要花一些篇幅詳細解釋一下。
而之所以我叫它”狀態保持”因為還有一個非常相似的場景可以類比:
用戶從A頁面提交表單到B頁面,如果提交數據校驗不通過,則需要返回A頁面,同時保持用戶在A頁面填寫的內容并提示用戶修改不對的地方。從提交到校驗出錯再返回繼續填寫是一個包含網絡交互的異步過程,這相當于填寫表單這個任務過會兒再繼續。
在傳統網頁開發中,用戶的狀態通過請求傳遞到服務端,交由后端狀態保持(類似交給db.query的currentSate)。而使用Ajax的網 頁,因為并未離開原頁面,那么服務端只要負責校驗用戶提交的數據是否正確即可,發送錯誤,返回錯誤處相關信息即可,這就是所謂前端狀態保持。可以看到這個 場景里邊服務端做的事情變少了,變純粹了。正如我們的例子中db.query不再存儲轉發第三個state參數,變得更在輕量。
我們看到通過JavaScript函數式語言特性,匿名函數支持和閉包很漂亮的解決了同步編程到異步編程轉化過程中遇到的一系列最重要的問題。但JavaScript是否就是最好的?這就要回答我們引用新技術時需要考慮的最后一個問題了。
使用Node.js是否帶來額外的困擾,如何解決?
Node.js性能真的是最好么?不用比較我們也可以得到結論,Node.js做無阻塞編程性能較難做到極致。何為極致?處理一個請求需要占用多少 內存,多少cpu資源,多少帶寬,有丁點浪費就不是極致。阻塞式編程浪費了大量進程資源只是在等待,導致大量內存和cpu的浪費。在這方面Node.js 好很多,但也正是因為一些閉包等JavaScript內建機制也會導致資源的浪費,看下面的代碼:
1 2 3 4 5 6 7 8 | function main(){ var id = 1; var str = ...; //這里局部變量str存儲一個2M的字符串 db.query(select name from persons where id= + id,function(name){ output(person id: + id + , name: + name);//n秒后數據返回后執行回調 }); } main(); |
至少整個數據查詢過程中,變量str所使用的2M內存并不會被釋放,而str保持下去可能并沒有意義。前面已經解釋過閉包的原理,閉包并沒有智能到只包起來今后可能被訪問到的對象。即使不了解閉包的原理,也可以通過一段簡單腳本驗證這點:
1 2 3 4 5 6 7 8 | function main(){ var id = 1; var str = ...; //這里存儲一個2M的字符串 window.setTimeout(function(){ debugger; //我們在這里設置斷點 },10000) } main(); |
我們在回調函數當中只設置一個斷點,并不指明我們要訪問哪個變量。然后我們在控制臺監視一下,id和str都是可以拿到的。
所以我來猜想一下,性能極端苛刻的場景,無阻塞是未來,但無阻塞發展下去,或者有更輕量的腳本引擎產生,或者JavaScript引擎可能要調整可以disable閉包,或者我們要通過給JS開發靜態編譯器在代碼發布前自動優化我們的代碼。
靜態編譯是如今JavaScript技術領域的又一個熱點,我們都知道JavaScript是解釋型腳本語言,在運行時自動編譯。但是運行時編譯只是將代碼轉為機器碼執行,卻并未覆蓋傳統編譯型語言在編譯階段所做的任務。比如,語法檢查,接口校驗,全局性能優化等等。
最常見的JavaScript靜態編譯就是腳本壓縮工具,在代碼發布到線上之前,我們通過各種壓縮工具,將代碼壓縮,達到減少網絡傳輸量的問題。而 在這個時間點,已經有越來越多的事情可做,比如:Google利用ClouserComplier提供的系列編譯指令,讓JavaScript更好的實現 OO編程。也有GWT,CoffeeScript這樣的項目,將其他語言編譯為JavaScript。在淘寶我們在代碼靜態編譯階段來解決因 JavaScript細粒度模塊化改造引入各種性能問題,也用來對第三方提供JavaScript代碼進行一定的安全檢查。
我們期待前面的代碼經過靜態編譯器編譯后變成如下結果:
1 2 3 4 5 6 7 8 9 | function main(){ var id = 1; var str = ...; //這里局部變量str存儲一個2M的字符串 db.query(select name from persons where id= + id,function(name){ output(person id: + id + , name: + name); }); str = ; //通過這一行,及時釋放不必要的內存占用。 } main(); |
除了性能方面的擔憂,使用Node.js進行編程增加了代碼編寫的復雜度。因為我們習慣于阻塞式編程的寫法,切換到異步模式編程,往往對于太多多層次的callback函數嵌套弄得不知所措。老趙最近開發了項目JSCEX正是要解決這個問題,它讓大家在遵守一些小的約定后,能夠仍然保持同步編程的寫法進行代碼開發。寫完的代碼同樣通過靜態編譯器編譯成異步回調式模式的代碼再交給JavaScript引擎執行。
Node.js還要解決什么問題
說了這么多,無阻塞編程要做的還遠不止這些。首先需要一個高效的JS引擎,高效的事件池和線程池。另外幾乎所有和Node.js交互的傳統模塊如文件系統,數據訪問,HTTP解析,DNS解析都是阻塞式的,都需要額外改造。
Node.js作者極其團隊,正是認清問題所在以及JS解決這些問題方面的優勢。基于Google開源的高效JavaScript引擎V8,貢獻了大量的智慧和精力解決上述大部分問題后才有Node.js橫空出世。
當前Node社區如此火熱,千余開源的Node.js模塊,活躍在WebFramework,WebSocket,RPC,模板引擎,數據抓取服務,圖形圖像幾乎所有工程領域。
后記
本文主要的信息來自Node.js作者在JSConf09,JSConf10上的分享。 而作為前端開發,著重講了函數式編程,閉包對于無阻塞開發的重要意義。我期待這篇文章能夠給前端和后端工程師都帶來收獲。
同樣作為前端開發,不得不再插幾句,說說服務端JS能夠解決的另一個問題:當前的Web開發前后端使用不同的語言,很多相同的業務邏輯要前后端分別 用不同語言重復實現。比如越來越多重度依賴JavaScript的胖客戶端應用,當客戶瀏覽器禁用JavaScript時,則需要使用服務端語言將主業務 流程再實現一次,這即是前端常說的”漸進增強”。
當我們擁有了服務端JavaScript語言,我們自然就會想到能否利用Node.js做到”一次開發,漸進增強”。解決掉這個為小量用戶,浪費大量時間的惱人的問題。這方面的實踐,YAHOO仍然是先驅,早在一年多前開始YAHOO通過nodejs-yui3項目做了很多卓越的貢獻,而淘寶自主開發的前端框架Kissy也有服務端運行的相關嘗試。
JavaScript在誕生之時就不僅僅是瀏覽器端工具,如今JavaScript能夠再一次回到服務端展示拳腳,感謝V8,感謝NodeJS作者,團隊和社區的諸多貢獻者,祝Node好運,JavaScript好運。本文引用地址:http://www.104case.com/article/185521.htm
評論