當前位置:首頁 > 模擬 > 模擬
[導(dǎo)讀]科普文一則,說說我對Node.js的一些認識,以及我作為前端工程師為什么會向后端工程師推薦Node.js。 “Node.js 是服務(wù)器端的 JavaScript 運行環(huán)境,它具有無阻塞(non-blocking)和事件驅(qū)動(event-driven)等的特色

科普文一則,說說我對Node.js的一些認識,以及我作為前端工程師為什么會向后端工程師推薦Node.js。
“Node.js 是服務(wù)器端的 JavaScript 運行環(huán)境,它具有無阻塞(non-blocking)和事件驅(qū)動(event-driven)等的特色,Node.js 采用V8引擎,同樣,Node.js實現(xiàn)了類似 Apache 和 nginx 的web服務(wù),讓你可以通過它來搭建基于 JavaScript的Web App。”
我想不僅僅是Node.js,當我們要引入任何一種新技術(shù)前都必須要搞清楚幾個問題:
1. 我們遇到了什么問題?
2. 這項新技術(shù)解決什么問題,是否契合我們遇到的問題?
3. 我們遇到問題的多種解決方案中,當前這項新技術(shù)的優(yōu)勢體現(xiàn)在哪兒?
4. 使用新技術(shù),帶來哪些新問題,嚴重么,我們能否解決掉?
我們的問題:Server端阻塞
Node.js被設(shè)計用來解決服務(wù)端阻塞問題.下面通過一段簡單的代碼解釋何為阻塞:

1
2
3
4
 
//根據(jù)ID,在數(shù)據(jù)庫中Persons表中查出Name
var name = db.query("select name from persons where id=1");
//進程等待數(shù)據(jù)查詢完畢,然后使用查詢結(jié)果。
output("name")

 


這段代碼的問題是在上面兩個語句之間,在整個數(shù)據(jù)查詢的過程中,當前程序進程往往只是在等待結(jié)果的返回.這就造成了進程的阻塞.對于高并發(fā),I/O 密集行的網(wǎng)絡(luò)應(yīng)用中,一方面進程很長時間處于等待狀態(tài),另一方面為了應(yīng)付新的請求不斷的增加新的進程.這樣的浪費會導(dǎo)致系統(tǒng)支持QPS遠遠小于后端數(shù)據(jù)服 務(wù)能夠支撐的QPS,成為了系統(tǒng)的瓶頸.而且這樣的系統(tǒng)也特別容易被慢鏈接攻擊(客戶端故意不接收或減緩接收數(shù)據(jù),加長進程等待時間)。
如何解決阻塞問題
可以引入事件處理機制解決這個問題。在查詢請求發(fā)起之前注冊數(shù)據(jù)加載事件的響應(yīng)函數(shù),請求發(fā)出之后立即將進程交出,而當數(shù)據(jù)返回后再觸發(fā)這個事件并在預(yù)定好的事件響應(yīng)函數(shù)中繼續(xù)處理數(shù)據(jù):
1
2
3
4
5
6
 
//定義如何后續(xù)數(shù)據(jù)處理函數(shù)
function onDataLoad(name){
   output("name");
}
//發(fā)起數(shù)據(jù)請求,同時指定數(shù)據(jù)返回后的回調(diào)函數(shù)
db.query("select name from persons where id=1",onDataLoad);

 


我們看到若按照這個思路解決阻塞問題,首先我們要提供一套高效的異步事件調(diào)度機制.而主要用于處理瀏覽器端的各種交互事件的JavaScript。相對于其他語言,至少有兩個關(guān)鍵點特別適合完成這個任務(wù)。
為什么JS適合解決阻塞問題
首先JavaScript是一種函數(shù)式編程語言,函數(shù)編程語言最重要的數(shù)學基礎(chǔ)是λ演算(lambda calculus) — 即函數(shù)對象可以作為其他函數(shù)對象的輸入(參數(shù))和輸出(返回值)。
這個特性使得為事件指定回調(diào)函數(shù)變得很容易。特別是JavaScript還支持匿名函數(shù)。通過匿名函數(shù)的輔助,之前的代碼可以進行簡寫如下:
1
2
3
 
db.query("select name from persons where id=1",function(name){
    output(name);
});

 


還有另一個關(guān)鍵問題是,異步回調(diào)的運行上下文保持(本文暫稱其為”狀態(tài)保持”)。我們先來看一段代碼來說明何為狀態(tài)保持:
1
2
3
4
5
6
7
 
//傳統(tǒng)同步寫法:將查詢和結(jié)果打印抽象為一個方法
function main(){
    var id = "1";
    var name = db.query("select name from persons where id=" + id);
    output("person id:" + id + ", name:" + name);
}
main();

 


前面的寫法在傳統(tǒng)的阻塞是編程中非常常見,但接下來進行異步改寫時會遇到一些困擾:
1
2
3
4
5
6
7
8
 
//異步寫法:
function main(){
    var id = "1";
    db.query("select name from persons where id=" + id,function(name){
        output("person id:" + id + ", name:" + name);//n秒后數(shù)據(jù)返回后執(zhí)行回調(diào)
    });
}
main();

 


細心的朋友可能已經(jīng)注意到,當?shù)却薾秒數(shù)據(jù)查詢結(jié)果返回后執(zhí)行回調(diào)時?;卣{(diào)函數(shù)中卻仍然使用了main函數(shù)的局部變量”id”,而”id”似乎應(yīng) 該在n秒前走出其作用域。為什么此時”id”仍然可以訪問呢,這是因為JavaScript的另外一個重要語言特性:閉包(Closures)。接下來我 來詳解閉包的原委。
在復(fù)雜的應(yīng)用中,我們一定會遇到這類場景。即在函數(shù)運行時需要訪問函數(shù)定義時的上下文數(shù)據(jù)(注意:一定要區(qū)分函數(shù)定義時和函數(shù)運行時兩個不同的時 刻)。特別是在異步編程模型中,函數(shù)的定義和運行又分處不同的時間段,那么保持上下文的問題變得更加突出了。因為我們在任務(wù)執(zhí)行一半時把資源交出去沒有問 題,但當任務(wù)需要再次繼續(xù)時我們必須還原現(xiàn)場。
在這個例子中,db.query作為一個公共的數(shù)據(jù)庫查詢方法,把”id”這個業(yè)務(wù)數(shù)據(jù)傳入給db.query,交由其保存是不太合適的。但我們可 以稍作抽象,讓db.query再支持一個需要保持狀態(tài)的數(shù)據(jù)對象傳入,當數(shù)據(jù)查詢完畢后可以把這些狀態(tài)數(shù)據(jù)原封不動的回傳。如下:
1
2
3
4
5
6
7
8
9
 
function main(){
    var id = "1";
    var currentState = new Object();
    currentState.person_id = id;
    db.query("select name from persons where id=" + id, function(name,state){
        output("person id:" + state.person_id + ", name:" + name);
    },currentState);//注意currentState是db.query的第三個參數(shù)
}
main();

 


記住這種重要的思路,我們再看看是否還能進一步的抽象?可以的,不過接下的動作之前,我們還要了解在JavaScript中一個函數(shù)也是一個對象。 一個函數(shù)實例fn除了函數(shù)體的定義之外,我們?nèi)匀豢梢栽谶@個函數(shù)對象實例之本身擴展其他屬性,如fn.a=1;受到這個啟發(fā)我們嘗試把需要保持的狀態(tài)直接 綁定到函數(shù)實例上:
1
2
3
4
5
6
7
8
9
10
 
function main(){
    var id = "1";
    var currentState = new Object();
    currentState.person_id = id;
    function onDataLoad(name){
        output("person id:" + onDataLoad.state.person_id + ", name:" + name);
    }
    onDataLoad.state = currentState ;//為函數(shù)指定state屬性,用于保持狀態(tài)
    db.query("select name from persons where id=" + id, onDataLoad);
}

 


我們做了什么?生成了currentState對象,然后在函數(shù)onDataLoad定義時,將currentState綁定給 onDataLoad這個函數(shù)實例。那么在onDataLoad運行時,就可以拿到定義時的state對象了。JavaScript的閉包特性就是內(nèi)置了 這個過程而已。
在每個JavaScript函數(shù)運行時,都有一個運行時內(nèi)部對象稱為Execution Context,它包含如下Variable Object(VO,變量對象), Scope Chain(作用域鏈)和”this” Value三部分。如圖:
 

其中變量對象VO,包含了所有局部變量的引用。對于main函數(shù),局部變量”id”存儲在VO.id內(nèi)??雌饋碛肰O來代替我們的 currentSate最合適了。但main函數(shù)還可能嵌套在其他函數(shù)之內(nèi),所以我們需要ScopeChain,它是一個包含當前運行函數(shù)VO和其所有父 函數(shù)scope的數(shù)組。
所以在這個例子中,在onDataLoad函數(shù)定義時,就為默認為其綁定了一個[[scope]]屬性指向其父函數(shù)的 ExecutionContext的ScopeChain。而當函數(shù)onDataLoad執(zhí)行時,就可以通過[[scope]]屬性來訪問父函數(shù)的VO對 象來找到id,如果父函數(shù)的VO中沒有id這個屬性,就再繼續(xù)向上查找其祖先的VO對象,直到找到id這個屬性或到達最外層返回undefined。也正 是因為這個引用,造成VO的引用計數(shù)不為0,在走出作用域時,才不會被垃圾回收。
很多朋友覺得閉包較難理解,其實我們只要能明確的區(qū)分函數(shù)定義和函數(shù)運行兩個時機,那么閉包就是讓函數(shù)在運行時能夠訪問到函數(shù)定義時的所處作用域內(nèi)的所有變量,或者說函數(shù)定義時能訪問到什么變量,那么在函數(shù)運行時通過相同的變量名一樣能訪問到。
關(guān)于狀態(tài)保持是本文的重點,在我看到的多數(shù)Node.js的介紹文章中并沒有詳解這里,我們只是知道了要解決阻塞問題,但是JavaScript解決阻塞問題的優(yōu)勢到底在哪里,作為一名前端工程師,我想有必要花一些篇幅詳細解釋一下。
而之所以我叫它”狀態(tài)保持”因為還有一個非常相似的場景可以類比:
用戶從A頁面提交表單到B頁面,如果提交數(shù)據(jù)校驗不通過,則需要返回A頁面,同時保持用戶在A頁面填寫的內(nèi)容并提示用戶修改不對的地方。從提交到校驗出錯再返回繼續(xù)填寫是一個包含網(wǎng)絡(luò)交互的異步過程,這相當于填寫表單這個任務(wù)過會兒再繼續(xù)。
在傳統(tǒng)網(wǎng)頁開發(fā)中,用戶的狀態(tài)通過請求傳遞到服務(wù)端,交由后端狀態(tài)保持(類似交給db.query的currentSate)。而使用Ajax的網(wǎng) 頁,因為并未離開原頁面,那么服務(wù)端只要負責校驗用戶提交的數(shù)據(jù)是否正確即可,發(fā)送錯誤,返回錯誤處相關(guān)信息即可,這就是所謂前端狀態(tài)保持??梢钥吹竭@個 場景里邊服務(wù)端做的事情變少了,變純粹了。正如我們的例子中db.query不再存儲轉(zhuǎn)發(fā)第三個state參數(shù),變得更在輕量。
我們看到通過JavaScript函數(shù)式語言特性,匿名函數(shù)支持和閉包很漂亮的解決了同步編程到異步編程轉(zhuǎn)化過程中遇到的一系列最重要的問題。但JavaScript是否就是最好的?這就要回答我們引用新技術(shù)時需要考慮的最后一個問題了。
使用Node.js是否帶來額外的困擾,如何解決?
Node.js性能真的是最好么?不用比較我們也可以得到結(jié)論,Node.js做無阻塞編程性能較難做到極致。何為極致?處理一個請求需要占用多少 內(nèi)存,多少cpu資源,多少帶寬,有丁點浪費就不是極致。阻塞式編程浪費了大量進程資源只是在等待,導(dǎo)致大量內(nèi)存和cpu的浪費。在這方面Node.js 好很多,但也正是因為一些閉包等JavaScript內(nèi)建機制也會導(dǎo)致資源的浪費,看下面的代碼:
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秒后數(shù)據(jù)返回后執(zhí)行回調(diào)
    });
}
main();

 


至少整個數(shù)據(jù)查詢過程中,變量str所使用的2M內(nèi)存并不會被釋放,而str保持下去可能并沒有意義。前面已經(jīng)解釋過閉包的原理,閉包并沒有智能到只包起來今后可能被訪問到的對象。即使不了解閉包的原理,也可以通過一段簡單腳本驗證這點:
1
2
3
4
5
6
7
8
 
function main(){
    var id = "1";
    var str = "..."; //這里存儲一個2M的字符串
    window.setTimeout(function(){
        debugger; //我們在這里設(shè)置斷點
    },10000)
}
main();

 


我們在回調(diào)函數(shù)當中只設(shè)置一個斷點,并不指明我們要訪問哪個變量。然后我們在控制臺監(jiān)視一下,id和str都是可以拿到的。
所以我來猜想一下,性能極端苛刻的場景,無阻塞是未來,但無阻塞發(fā)展下去,或者有更輕量的腳本引擎產(chǎn)生,或者JavaScript引擎可能要調(diào)整可以disable閉包,或者我們要通過給JS開發(fā)靜態(tài)編譯器在代碼發(fā)布前自動優(yōu)化我們的代碼。
靜態(tài)編譯是如今JavaScript技術(shù)領(lǐng)域的又一個熱點,我們都知道JavaScript是解釋型腳本語言,在運行時自動編譯。但是運行時編譯只是將代碼轉(zhuǎn)為機器碼執(zhí)行,卻并未覆蓋傳統(tǒng)編譯型語言在編譯階段所做的任務(wù)。比如,語法檢查,接口校驗,全局性能優(yōu)化等等。
最常見的JavaScript靜態(tài)編譯就是腳本壓縮工具,在代碼發(fā)布到線上之前,我們通過各種壓縮工具,將代碼壓縮,達到減少網(wǎng)絡(luò)傳輸量的問題。而 在這個時間點,已經(jīng)有越來越多的事情可做,比如:Google利用ClouserComplier提供的系列編譯指令,讓JavaScript更好的實現(xiàn) OO編程。也有GWT,CoffeeScript這樣的項目,將其他語言編譯為JavaScript。在淘寶我們在代碼靜態(tài)編譯階段來解決因 JavaScript細粒度模塊化改造引入各種性能問題,也用來對第三方提供JavaScript代碼進行一定的安全檢查。
我們期待前面的代碼經(jīng)過靜態(tài)編譯器編譯后變成如下結(jié)果:
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 = ""; //通過這一行,及時釋放不必要的內(nèi)存占用。
}
main();

 


除了性能方面的擔憂,使用Node.js進行編程增加了代碼編寫的復(fù)雜度。因為我們習慣于阻塞式編程的寫法,切換到異步模式編程,往往對于太多多層次的callback函數(shù)嵌套弄得不知所措。老趙最近開發(fā)了項目JSCEX正是要解決這個問題,它讓大家在遵守一些小的約定后,能夠仍然保持同步編程的寫法進行代碼開發(fā)。寫完的代碼同樣通過靜態(tài)編譯器編譯成異步回調(diào)式模式的代碼再交給JavaScript引擎執(zhí)行。
Node.js還要解決什么問題
說了這么多,無阻塞編程要做的還遠不止這些。首先需要一個高效的JS引擎,高效的事件池和線程池。另外幾乎所有和Node.js交互的傳統(tǒng)模塊如文件系統(tǒng),數(shù)據(jù)訪問,HTTP解析,DNS解析都是阻塞式的,都需要額外改造。
Node.js作者極其團隊,正是認清問題所在以及JS解決這些問題方面的優(yōu)勢?;贕oogle開源的高效JavaScript引擎V8,貢獻了大量的智慧和精力解決上述大部分問題后才有Node.js橫空出世。
當前Node社區(qū)如此火熱,千余開源的Node.js模塊,活躍在WebFramework,WebSocket,RPC,模板引擎,數(shù)據(jù)抓取服務(wù),圖形圖像幾乎所有工程領(lǐng)域。
后記
本文主要的信息來自Node.js作者在JSConf09,JSConf10上的分享。 而作為前端開發(fā),著重講了函數(shù)式編程,閉包對于無阻塞開發(fā)的重要意義。我期待這篇文章能夠給前端和后端工程師都帶來收獲。
同樣作為前端開發(fā),不得不再插幾句,說說服務(wù)端JS能夠解決的另一個問題:當前的Web開發(fā)前后端使用不同的語言,很多相同的業(yè)務(wù)邏輯要前后端分別 用不同語言重復(fù)實現(xiàn)。比如越來越多重度依賴JavaScript的胖客戶端應(yīng)用,當客戶瀏覽器禁用JavaScript時,則需要使用服務(wù)端語言將主業(yè)務(wù) 流程再實現(xiàn)一次,這即是前端常說的”漸進增強”。
當我們擁有了服務(wù)端JavaScript語言,我們自然就會想到能否利用Node.js做到”一次開發(fā),漸進增強”。解決掉這個為小量用戶,浪費大量時間的惱人的問題。這方面的實踐,YAHOO仍然是先驅(qū),早在一年多前開始YAHOO通過nodejs-yui3項目做了很多卓越的貢獻,而淘寶自主開發(fā)的前端框架Kissy也有服務(wù)端運行的相關(guān)嘗試。
JavaScript在誕生之時就不僅僅是瀏覽器端工具,如今JavaScript能夠再一次回到服務(wù)端展示拳腳,感謝V8,感謝NodeJS作者,團隊和社區(qū)的諸多貢獻者,祝Node好運,JavaScript好運。

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉