作為一個(gè)在Sun微系統(tǒng)公司Java SE團(tuán)隊(duì)工作了十多年的人,難道不應(yīng)該是體內(nèi)流淌著Java字節(jié)碼的血、只要一息尚存就要不斷實(shí)現(xiàn)抽象接口嗎?但對(duì)于我這個(gè)前Java SE團(tuán)隊(duì)成員來(lái)說(shuō),2011年學(xué)習(xí)了Node.js平臺(tái)后就像是呼吸到了新鮮空氣一樣——我在2009年1月被Sun裁退之后(正好在Oracle收購(gòu)之前),開始學(xué)習(xí)Node.js并被它深深所吸引。
我是怎樣被吸引的?從2010年起,我就開始寫各種關(guān)于Node.js編程的東西了。具體來(lái)說(shuō),寫了四版《Node.js Web開發(fā)》,加上一些其他的書,和數(shù)不清的關(guān)于Node.js編程的教程。可以說(shuō),我花了非常多的時(shí)間解釋Node.js和JavaScript語(yǔ)言的發(fā)展。
在Sun微系統(tǒng)工作時(shí),我相信一切都能用Java描述。我在JavaONE上演講,與別人共同開發(fā)了java.awt.Robot類,舉辦了Mustang Regressions Contest(為Java 1.6發(fā)布準(zhǔn)備的找bug競(jìng)賽),幫助別人啟動(dòng)了“Java的分布式授權(quán)”,就是在OpenJDK出現(xiàn)之前為L(zhǎng)inux發(fā)行版發(fā)布JDK的解決方案,后來(lái)還在啟動(dòng)OpenJDK項(xiàng)目上扮演了一個(gè)小角色。
一路走來(lái),我在java.net上有過(guò)一個(gè)博客(現(xiàn)在荒廢了),連續(xù)六年堅(jiān)持每周寫一到兩篇文章,討論Java生態(tài)系統(tǒng)中發(fā)生的一切。最常見的話題就是反駁那些唱衰Java的論調(diào)。
Duke獎(jiǎng),頒發(fā)給努力超越自己的員工。我在舉辦了Mustang Regressions Contest這個(gè)為Java 1.6發(fā)布而準(zhǔn)備的找bug競(jìng)賽之后獲得了這個(gè)獎(jiǎng)勵(lì)。
那么,說(shuō)好的靠著Java字節(jié)碼生存和呼吸呢?我這篇文章的目的就是想解釋下一個(gè)Java字節(jié)碼的忠實(shí)粉絲是如何變成了Node.js/JavaScript的傳道者。
其實(shí)并不是說(shuō)我和Java完全不相干了。
過(guò)去三年里我也寫了許多Java/Spring/Hibernate代碼。雖然我很喜歡我的工作——我在Solar Industry工作,做一些實(shí)現(xiàn)夢(mèng)想的事情,如寫數(shù)據(jù)庫(kù)查詢語(yǔ)句查詢用電量等,但用Java編程已經(jīng)是昨日黃花了。
兩年的Spring編程讓我清楚地意識(shí)到一件事:掩蓋復(fù)雜性并不會(huì)讓其變簡(jiǎn)單,只會(huì)欲蓋彌彰。
本文要點(diǎn):
Java包含了大量樣板代碼,擾亂了程序員的意圖。Spring和Spring Boot的教訓(xùn):掩蓋復(fù)雜性只會(huì)讓事情更復(fù)雜性。Java EE是個(gè)“由委員會(huì)設(shè)計(jì)”的項(xiàng)目,覆蓋了企業(yè)應(yīng)用開發(fā)所需的一切,導(dǎo)致過(guò)度復(fù)雜。Spring的編程體驗(yàn)非常好,但是一旦在子系統(tǒng)深處出現(xiàn)模糊難懂、從未見過(guò)的異常信息,就需要花掉三天以上才能找出問(wèn)題是什么。如果框架允許程序員完全不寫代碼,那產(chǎn)生的額外開銷會(huì)有多少?雖然像Eclipse之類的IDE很強(qiáng)大,但都是Java復(fù)雜度的癥狀。Node.js是一個(gè)人磨礪并精煉輕量級(jí)事件驅(qū)動(dòng)架構(gòu)的結(jié)果,直到Node.js自己揭露了真相。JavaScript社區(qū)似乎很感謝去掉樣板代碼,可以讓程序員專注做有意義的事。回調(diào)陷阱的解決方案async/await函數(shù)就是移除樣板代碼的例子。用Node.js寫程序很愉快。JavaScript缺少Java那種嚴(yán)格類型檢查,但這是個(gè)雙刃劍。編程變得容易了許多,但需要更多測(cè)試才能保證正確。npm/yarn包管理器非常優(yōu)秀,也非常好用,相對(duì)的就是令人生厭的Maven。Java和Node.js都提供優(yōu)秀的性能,這與“JavaScript很慢因此Node.js的性能必然不好”的傳說(shuō)正相反。Node.js的性能要?dú)w功于Google為了加速Chrome瀏覽器而在V8上的投入。瀏覽器之間的激烈競(jìng)爭(zhēng)使得JavaScript變得越來(lái)越強(qiáng)大,反過(guò)來(lái)幫助了Node.js。
歡迎工作一到五年的Java工程師朋友們加入Java技術(shù)交流:611481448
群內(nèi)提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用、高并發(fā)、高性能及分布式、Jvm性能調(diào)優(yōu)、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個(gè)知識(shí)點(diǎn)的架構(gòu)資料)合理利用自己每一分每一秒的時(shí)間來(lái)學(xué)習(xí)提升自己,不要再用"沒有時(shí)間“來(lái)掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來(lái)的自己一個(gè)交代!
Java已成為負(fù)擔(dān),用Node.js編程很愉快
一些工具或?qū)ο笫窃O(shè)計(jì)師多年精心磨礪并提煉的結(jié)果。他們會(huì)嘗試不同的想法,去掉不需要的特性,最后得到為某個(gè)目的量身打造的對(duì)象。因此這些對(duì)象都有強(qiáng)大的簡(jiǎn)單性,所以非常吸引人。而Java并不是這種系統(tǒng)。
Spring是個(gè)流行的Java Web應(yīng)用程序開發(fā)框架。Spring,特別是Spring Boot,其核心目標(biāo)是個(gè)預(yù)配置的、易用的Java EE棧。Spring程序員不需要考慮所有servlets、數(shù)據(jù)持久、應(yīng)用服務(wù)器,以及構(gòu)成系統(tǒng)的其他不知所云的東西。Spring會(huì)處理這一切,而你只需要專注寫代碼即可。例如,JPA Repository類會(huì)將數(shù)據(jù)庫(kù)查詢合成為方法,名字類似于“findUserByFirstName”,這樣你無(wú)需寫任何代碼,只需要調(diào)用方法,Spring就會(huì)處理剩余的一切。
——這個(gè)想法很不錯(cuò),而且的確很好用,直到某種情況發(fā)生。
如果你得到一個(gè)Hibernate PersistentObjectException,提示“ detached entity passed to persist”,也就是說(shuō)到達(dá)REST訪問(wèn)點(diǎn)的JSON有ID值,而這層含義往往需要幾天時(shí)間才能理解。這就是過(guò)度簡(jiǎn)化的代價(jià)。Hibernate也在過(guò)度簡(jiǎn)化,它希望控制ID值,于是拋出了這個(gè)不知所云的異常。在Spring的棧中,子系統(tǒng)一個(gè)接一個(gè),它就像復(fù)仇女神一樣耐心地等待你犯哪怕最微小的錯(cuò)誤,然后用應(yīng)用程序崩潰的異常來(lái)懲罰你。
緊接著你就會(huì)看到巨大的棧跟蹤信息。它們有好幾個(gè)屏幕那么長(zhǎng),充滿了一個(gè)又一個(gè)抽象方法。Spring顯然做了許多工作來(lái)實(shí)現(xiàn)代碼的功能。這種級(jí)別的抽象顯然需要大量的邏輯,來(lái)找出所有信息并執(zhí)行請(qǐng)求。長(zhǎng)長(zhǎng)的棧跟蹤不一定是壞事,它指出了一個(gè)癥狀:這需要多少內(nèi)存和性能上的額外開銷?
既然程序員不需要寫任何代碼,那么調(diào)用“findUserByFirstName”時(shí),它是怎么執(zhí)行的?框架需要解析方法名、猜測(cè)程序員的意圖、構(gòu)建類似于抽象語(yǔ)法樹的東西、生成SQL等等。這些事情的額外開銷有多大?一切都只為了讓程序員不需要寫代碼?
在經(jīng)歷了多次折磨,浪費(fèi)了許多天的時(shí)間學(xué)習(xí)本來(lái)不需要學(xué)習(xí)的東西后,你也許會(huì)產(chǎn)生與我同樣的迷惑:掩蓋復(fù)雜性不會(huì)產(chǎn)生簡(jiǎn)單性,只會(huì)讓系統(tǒng)更復(fù)雜。
而使用Node.js的關(guān)鍵就是這一點(diǎn)。
“兼容性很重要”是句很好的口號(hào),它的意思是Java平臺(tái)的主要價(jià)值體現(xiàn)在它完全后向兼容。我們很看中這一點(diǎn),連T恤衫上都印了這句口號(hào)。當(dāng)然,維持這種程度的兼容性非常痛苦,有時(shí)候還可以用來(lái)避免那些已經(jīng)沒用的老方法。
Node.js正好相反
Spring和Java EE過(guò)度復(fù)雜,而Node.js完全是新鮮空氣。
首先是Ryan Dahl在開發(fā)Node.js平臺(tái)核心時(shí)采用的設(shè)計(jì)美學(xué)。Dahl的經(jīng)驗(yàn)是,線程會(huì)導(dǎo)致重量級(jí)的復(fù)雜系統(tǒng)。他想找一些不同的東西,花了很長(zhǎng)時(shí)間打磨并提煉了一系列核心思想放到了Node.js里。結(jié)果就是一個(gè)輕量級(jí)、單線程的系統(tǒng),巧妙地利用了JavaScript的匿名函數(shù)進(jìn)行異步回調(diào),還有一個(gè)巧妙的運(yùn)行時(shí)庫(kù)用來(lái)實(shí)現(xiàn)異步。最初的基調(diào)是通過(guò)回調(diào)函數(shù)的事件發(fā)布來(lái)實(shí)現(xiàn)高吞吐量的事件處理。
然后就是JavaScript語(yǔ)言本身了。JavaScript程序員似乎很喜歡移除樣板代碼,使得程序員可以專注于有用的事情。
另一個(gè)用來(lái)對(duì)比Java和JavaScript的例子就是事件處理函數(shù)的實(shí)現(xiàn)。在Java中,事件處理函數(shù)需要?jiǎng)?chuàng)建一個(gè)實(shí)際的抽象接口類。這就需要許多冗長(zhǎng)的代碼,使得代碼本身的意圖含混不清。程序員的意圖埋在那一大堆樣板代碼后面,誰(shuí)能看得清呢?
而在JavaScript中,你只需要簡(jiǎn)單地使用匿名函數(shù),也就是閉包。不需要搜索正確的抽象接口,只需要寫下必須的代碼,沒有任何冗余。于是有了另一個(gè)教訓(xùn):大多數(shù)編程語(yǔ)言只會(huì)掩蓋程序員的意圖,使得代碼更難理解。
這就是使用Node.js的最大好處。不過(guò)我們還得解決一個(gè)問(wèn)題:回調(diào)陷阱。
有時(shí)解決方案就蘊(yùn)藏在問(wèn)題里
JavaScript的異步編程一直有兩個(gè)問(wèn)題。一個(gè)就是Node.js中所謂的“回調(diào)陷阱”。很容易就陷入嵌套回調(diào)函數(shù)的陷阱中,每層嵌套都會(huì)讓代碼更復(fù)雜,使得錯(cuò)誤處理和結(jié)果處理更困難。一個(gè)相關(guān)的問(wèn)題就是JavaScript語(yǔ)言不會(huì)幫助程序員恰當(dāng)?shù)乇磉_(dá)異步執(zhí)行。
一些庫(kù)使用Promise來(lái)簡(jiǎn)化異步執(zhí)行。這就是另一個(gè)掩蓋復(fù)雜度使之更復(fù)雜的例子。
舉例來(lái)說(shuō):
constasync=require(‘a(chǎn)sync’);constfs =require(‘fs’);constcat =function(filez, fini){async.eachSeries(filez,function(filenm, next){ fs.readFile(filenm, ‘utf8’,function(err, data){if(err)returnnext(err); process.stdout.write(data, ‘utf8’,function(err){if(err) next(err);elsenext(); }); }); },function(err){if(err) fini(err);elsefini(); });};cat(process.argv.slice(2),function(err){if(err)console.error(err.stack);});
這段代碼實(shí)現(xiàn)了Unix的cat命令。async庫(kù)用來(lái)簡(jiǎn)化異步執(zhí)行序列很不錯(cuò),但它用了許多樣板代碼,混淆了程序員的真實(shí)意圖。
我們實(shí)際想寫的是個(gè)循環(huán)。但不能寫成循環(huán),而且也不是自然的循環(huán)結(jié)構(gòu)。進(jìn)一步,錯(cuò)誤處理和結(jié)果處理不在最自然的地方,而是違反常規(guī)地寫到了回調(diào)函數(shù)內(nèi)。在Node.js支持ES2015/2016之前,這是人們能做到的最好方法。
在Node.js 10.x中的等價(jià)寫法是:
constfs =require(‘fs’).promises;asyncfunctioncat(filenmz){for(varfilenmoffilenmz) {letdata =awaitfs.readFile(filenm, ‘utf8’);awaitnewPromise((resolve, reject) =>{ process.stdout.write(data, ‘utf8’, (err) => {if(err) reject(err);elseresolve(); }); }); }}cat(process.argv.slice(2)).catch(err=>{console.error(err.stack); });
這段代碼用async/await函數(shù)重寫了前面的例子。還是同樣的異步結(jié)構(gòu),但使用了正常的循環(huán)結(jié)構(gòu)來(lái)書寫。錯(cuò)誤和結(jié)果處理的位置也很自然,代碼更易于理解,更容易編寫,而且也可以很容易地理解程序員的意圖。
唯一的遺憾就是process.stdout.write不提供Promise接口,因此不能干凈地使用async函數(shù)表達(dá),只能再包裹一層Promise。
回調(diào)陷阱并不是用掩蓋復(fù)雜性的方式解決的。相反,語(yǔ)言和范式的改變解決了回調(diào)陷阱的問(wèn)題,同時(shí)還解決了過(guò)多樣板代碼的問(wèn)題。有了async函數(shù),代碼就更漂亮了。
盡管最初這是Node.js的缺點(diǎn),但優(yōu)美的解決方案將缺點(diǎn)變成了Node.js和JavaScript的優(yōu)點(diǎn)。
自定義良好的類型和接口
我之所以是Java的死忠,原因之一就是嚴(yán)格的類型檢查使得Java可以用于編寫巨型應(yīng)用。當(dāng)時(shí)的風(fēng)向是編寫宏系統(tǒng)(沒有什么微服務(wù)、Docker之類的),由于Java有嚴(yán)格的類型檢查,Java編譯器可以幫你避免許多類型的bug,因?yàn)椴缓玫拇a無(wú)法通過(guò)編譯。
相反,JavaScript的類型很松散。理論也很明顯:程序員無(wú)法確定他們收到的對(duì)象的類型,那他們?cè)趺粗涝撟鍪裁茨兀?/p>
Java的強(qiáng)類型的缺點(diǎn)就是太多樣板代碼。程序員要不斷進(jìn)行類型轉(zhuǎn)換,否則就得努力保證一切都分毫不差。程序員要花掉很多時(shí)間寫極其精確的代碼,使用更多的樣板代碼,以圖早期發(fā)現(xiàn)錯(cuò)誤并改正。
這個(gè)問(wèn)題十分嚴(yán)重,因此人們必須使用大型、復(fù)雜的IDE。簡(jiǎn)單的編輯器是不夠的。讓Java程序員保持正常的唯一方式就是提供下拉菜單供他選擇對(duì)象中可用的字段,描述方法的參數(shù),幫助他創(chuàng)建類,協(xié)助他做重構(gòu),以及其他一切Eclipse、NetBeans和IntelliJ能提供的功能。
還有……別逼我說(shuō)Maven。那個(gè)工具太垃圾了。
在JavaScript中,許多類型不需要定義,通常也不需要用類型轉(zhuǎn)換。因此代碼更清晰易讀,但存在漏掉編碼錯(cuò)誤的風(fēng)險(xiǎn)。
在這一點(diǎn)上Java是好是壞取決于你的觀點(diǎn)。我十年前認(rèn)為,用這些額外開銷獲得更多確定性是值得的。但我現(xiàn)在認(rèn)為,我靠怎么要寫這么多代碼,還是JavaScript簡(jiǎn)單。
用容易測(cè)試的小模塊來(lái)對(duì)抗bug
Node.js鼓勵(lì)程序員將程序分割成小單元,即模塊。看似是一件小事,但卻部分地解決了剛才提到的問(wèn)題。
模塊是:
自我包含:顧名思義,模塊把相關(guān)的代碼包裝成一個(gè)單位;
強(qiáng)邊界:模塊內(nèi)部的代碼不會(huì)被其他地方的代碼侵入;
顯式導(dǎo)出:默認(rèn)情況下模塊中的代碼和數(shù)據(jù)不會(huì)被導(dǎo)出,只有選中的函數(shù)和代碼才能被別人使用;
顯式導(dǎo)入:模塊需要定義它依賴哪些模塊;
潛在的獨(dú)立性:很容易將模塊公開發(fā)布到npm代碼倉(cāng)庫(kù)中,或者發(fā)布到其他私有倉(cāng)庫(kù)中供其他應(yīng)用使用;
易于理解:需要閱讀的代碼量小,因此更容易理解代碼的意圖;
易于測(cè)試:如果實(shí)現(xiàn)正確,那么小模塊可以很容易進(jìn)行單元測(cè)試。
所有這些特性一起,使得Node.js模塊更容易測(cè)試,并且有定義良好的范圍。
人們對(duì)JavaScripot的恐懼一般集中在它缺乏嚴(yán)格的類型檢查,因此代碼很容易出錯(cuò)。對(duì)于小型、目的明確并且有著清晰邊界的模塊來(lái)說(shuō),受影響的范圍通常會(huì)限制在模塊內(nèi)部。因此大多數(shù)情況下需要考慮的范圍很小,而且都安全地保護(hù)在模塊的邊界內(nèi)部。
解決弱類型問(wèn)題的另一個(gè)方案就是增加測(cè)試。
通過(guò)書寫簡(jiǎn)單的JavaScript代碼而節(jié)省下的時(shí)間,必須花一部分在增加測(cè)試上。測(cè)試用例必須捕獲那些本應(yīng)被編譯器捕獲的錯(cuò)誤。你肯定會(huì)測(cè)試代碼的,對(duì)吧?
如果想在JavaScript中享受靜態(tài)類型檢查,可以試試TypeScript。我沒用過(guò)TypeScript,但聽說(shuō)它很不錯(cuò)。它增加了包括類型檢查在內(nèi)的許多有用的功能,并且可以直接編譯成兼容的JavaScript。
因此在這一點(diǎn)上,Node.js和JavaScript完勝。
包管理
Maven想想就覺得可怕,完全不知道該寫什么。我覺得,肯定有人非常喜歡Maven,也肯定有人很討厭Maven,兩者之間沒有中間地帶。
Java生態(tài)環(huán)境的問(wèn)題之一就是它沒有統(tǒng)一的包管理系統(tǒng)。Maven包還算可以,而且理論上應(yīng)該能在Gradle中使用。但不論是用途、易用性還是功能上,Maven與Node.js的包管理系統(tǒng)相比簡(jiǎn)直是天壤之別。
在Node.js的世界里有兩個(gè)非常優(yōu)秀的包管理系統(tǒng),他們能合作得很好。最初只有npm和npm的代碼倉(cāng)庫(kù)。
npm用一種非常好的格式描述包的依賴關(guān)系。依賴可以是嚴(yán)格的(精確的版本1.2.3),或者可以逐漸增加較松散的條件,直到使用“*”表示任何最新版本。Node.js社區(qū)已經(jīng)向npm代碼倉(cāng)庫(kù)發(fā)布了幾十萬(wàn)個(gè)包。在npm代碼倉(cāng)庫(kù)之外使用這些包也同樣容易。
最好的地方是npm代碼庫(kù)不僅供Node.js使用,也可以讓前端工程師使用。以前他們使用類似于Bower之類的包管理工具。現(xiàn)在,Bower已經(jīng)過(guò)時(shí),所有的前端JavaScript庫(kù)都以npm包的形式存在。許多前端工具鏈如Vue.js CLI和Webpack都是用Node.js編寫的。
Node.js的另一個(gè)包管理器yarn從npm代碼倉(cāng)庫(kù)下載包,而且使用與npm相同的配置文件。yarn與npm相比的主要優(yōu)勢(shì)就是運(yùn)行得更快。
不論是用npm還是yarn,npm代碼倉(cāng)庫(kù)都是使得Node.js如此易用和愉快的重要因素。
在創(chuàng)建了java.awt.Robot之后,我想出了這張圖。官方的Duke吉祥物完全由曲線組成,而RoboDuke則都是直線,除了肘關(guān)節(jié)處的齒輪之外。
性能
Java和JavaScript都被批評(píng)過(guò)太慢。
兩者都由編譯器將源代碼轉(zhuǎn)換成字節(jié)碼,再由虛擬機(jī)執(zhí)行。VM通常會(huì)將字節(jié)碼再次編譯成原生代碼,并使用各種優(yōu)化技術(shù)。
Java和JavaScript在性能方面都有巨大的需求。Java和Node.js需要快速的服務(wù)器端代碼,在瀏覽器中的JavaScript則需要更好的客戶端應(yīng)用性能。
Sun/Oracle JDK使用HotSpot這個(gè)超級(jí)虛擬機(jī),它采用了多字節(jié)編譯策略。它的名字表示,它會(huì)檢測(cè)經(jīng)常執(zhí)行的代碼,一段代碼執(zhí)行次數(shù)越多,就會(huì)應(yīng)用越多的優(yōu)化。因此HotSopt可以產(chǎn)生非常快的代碼。
而對(duì)于JavaScript,我們?cè)欢让曰螅赫l(shuí)能期待瀏覽器中運(yùn)行的JavaScript能實(shí)現(xiàn)任何復(fù)雜應(yīng)用程序呢?辦公文檔套件肯定沒辦法用JavaScript在瀏覽器中實(shí)現(xiàn)吧?但今日,這一切都實(shí)現(xiàn)了。本文就是用Google Docs寫的,它的性能還不錯(cuò),瀏覽器上運(yùn)行的JavaScript性能每年都有大幅度增長(zhǎng)。
這個(gè)增長(zhǎng)趨勢(shì)使得Node.js越來(lái)越好,因?yàn)樗玫木褪荂hrome的V8引擎。
機(jī)器學(xué)習(xí)領(lǐng)域涉及到大量數(shù)學(xué)計(jì)算,因此數(shù)據(jù)科學(xué)家通常使用R或Python。包括機(jī)器學(xué)習(xí)在內(nèi)的幾個(gè)領(lǐng)域都需要快速數(shù)值計(jì)算。許多原因?qū)е翵avaScript很不擅長(zhǎng)數(shù)值計(jì)算,但人們已經(jīng)在努力開發(fā)一個(gè)標(biāo)準(zhǔn)庫(kù),使得JavaScript也可以進(jìn)行數(shù)值計(jì)算。
JavaScript還可以使用Tensorflow中的一個(gè)新的庫(kù):TensorFlow.js。它的API類似于Python的TensorFlow,可以導(dǎo)入訓(xùn)練好的模型,用來(lái)做比如分析動(dòng)態(tài)視頻以識(shí)別訓(xùn)練過(guò)的物體等工作,而且可以完全在瀏覽器中運(yùn)行。
此前IBM的Chris Bailey在介紹Node.js的性能和擴(kuò)展性問(wèn)題時(shí),就介紹了關(guān)于Docker/Kubernetes部署方面的問(wèn)題。他從一系列性能評(píng)測(cè)開始談起,證明Node.js在I/O吞吐量、應(yīng)用程序啟動(dòng)時(shí)間和內(nèi)存足跡方面的性能已遠(yuǎn)遠(yuǎn)超過(guò)了Spring Boot。而且,由于V8引擎的改進(jìn),Node.js的每次發(fā)布都會(huì)帶來(lái)巨大的性能提升。
Bailey還表示,人們不應(yīng)該在Node.js運(yùn)行計(jì)算類的代碼。理解其原因非常重要。因?yàn)镹ode.js是單線程模型,長(zhǎng)時(shí)間運(yùn)行的計(jì)算會(huì)阻塞事件的執(zhí)行。在我的《Node.js Web開發(fā)》一書中,我談到了這個(gè)問(wèn)題,并介紹了三種方法:
算法重構(gòu):找出算法中慢的部分并進(jìn)行重構(gòu)以獲得更快的速度;
將計(jì)算代碼用事件分發(fā)機(jī)制分成小塊,這樣Node.js可以經(jīng)常返回到執(zhí)行線程上;
將計(jì)算交給后臺(tái)服務(wù)器。
如果JavaScript的進(jìn)步還達(dá)不到應(yīng)用程序的要求,那么還有兩種方法可以直接在Node.js中集成原生代碼。Node.js的工具鏈包括node-gyp,它能處理鏈接原生代碼模塊的工作。WebAssembly能將其他語(yǔ)言編譯成一個(gè)執(zhí)行速度很快的JavaScript子集。WebAssembly是一種可執(zhí)行代碼的便攜式格式,可以在JavaScript引擎中運(yùn)行。
富互聯(lián)網(wǎng)應(yīng)用(RIA)
十年前軟件行業(yè)談?wù)摰脑掝}就是,用快速的JavaScript引擎運(yùn)行富互聯(lián)網(wǎng)應(yīng)用,從而使得桌面應(yīng)用失去存在的必要。
實(shí)際上,這件事情20年前就開始了。Sun和Netscape達(dá)成了一項(xiàng)協(xié)議,在Netscape瀏覽器中運(yùn)行Java Applets。當(dāng)時(shí)JavaScript語(yǔ)言是作為編寫Java Applets的腳本語(yǔ)言的一部分出現(xiàn)的。當(dāng)時(shí)的希望是在服務(wù)器端運(yùn)行Java Servlets,在客戶端運(yùn)行Java Applets,從而達(dá)到前后端使用同一種語(yǔ)言的目的。但由于許多原因,這個(gè)目標(biāo)并沒有實(shí)現(xiàn)。
十年前,JavaScript開始變得足夠強(qiáng)大,可以用來(lái)實(shí)現(xiàn)復(fù)雜的應(yīng)用程序了。因此出現(xiàn)了RIA這個(gè)詞,而且RIA據(jù)稱將在客戶端應(yīng)用的平臺(tái)上干掉Java。
今天我們可以看到,RIA的想法已經(jīng)實(shí)現(xiàn)了。通過(guò)服務(wù)器端的Node.js,我們終于可以實(shí)現(xiàn)了當(dāng)年的目標(biāo),但兩側(cè)的語(yǔ)言卻都是JavaScript。
舉一些例子:
Google Docs(這篇文章的協(xié)作工具),類似于傳統(tǒng)的辦公套件,但完全在瀏覽器中運(yùn)行。
強(qiáng)大的框架,如React、Angular、Vue.js,它們HTML/CSS進(jìn)行渲染,極大地簡(jiǎn)化了基于瀏覽器的應(yīng)用開發(fā)。
Electron是個(gè)Node.js的Chromium瀏覽器的混合物,它支持桌面應(yīng)用的跨平臺(tái)開發(fā)。許多非常流行的應(yīng)用程序都是用Electron開發(fā)的,如Visual Studio Code、Atom、GitKraken、Postman等,性能都非常好。
由于Electron/NW.js使用了瀏覽器引擎,React/Angular/Vue等框架就都能在桌面應(yīng)用中使用了。例如這篇文章:https://blog.sourcerer.io/creating-a-markdown-editor-previewer-in-electron-and-vue-js-32a084e7b8fe。
Java在桌面應(yīng)用平臺(tái)上失敗的原因并不是JavaScript的RIA,主要原因是Sun微系統(tǒng)對(duì)于客戶端技術(shù)的無(wú)視。Sun專注于要求快速服務(wù)器端性能的企業(yè)客戶。當(dāng)時(shí)我就在Sun,對(duì)此親眼目睹。真正殺死Applets的是幾年前在Java插件和Java Web Start中的一個(gè)嚴(yán)重的安全漏洞。那個(gè)漏洞造成了全世界的恐慌,于是人們都不再使用Java applets和Webstart應(yīng)用了。
其他Java桌面應(yīng)用依然能夠開發(fā),而且NetBeans和Eclipse IDE之間的競(jìng)爭(zhēng)現(xiàn)在依然火熱。但這個(gè)領(lǐng)域的Java開發(fā)已經(jīng)是一潭死水了,而且除了一些開發(fā)者工具之外,已經(jīng)很少見到基于Java的應(yīng)用程序了。
一個(gè)例外是JavaFX。
JavaFX是十年前Sun為對(duì)抗iPhone而提出的方案。它計(jì)劃支持在手機(jī)中的Java平臺(tái)上開發(fā)富界面的應(yīng)用程序,從而將Flash和iOS應(yīng)用程序驅(qū)逐出市場(chǎng)。結(jié)果沒有發(fā)生。JavaFX依然有人使用,但并不像它宣稱的那么火熱。
而這個(gè)領(lǐng)域的一切狂熱都來(lái)自于React、Vue.js和類似框架的出現(xiàn)。
因此,在這一點(diǎn)上JavaScript和Node.js獲得了碾壓式的勝利。
Java戒指,是早期的一次Java ONE會(huì)議的東西。這些戒指上包含芯片,內(nèi)部完全是用Java實(shí)現(xiàn)的。在JavaONE上的主要用途是解鎖大廳里的電腦。
Java戒指的說(shuō)明書。
結(jié)論
如今,開發(fā)服務(wù)器端代碼有許多選擇。我們不必再被局限在“P語(yǔ)言”(Perl,PHP,Python)和Java中,因?yàn)槲覀冇蠳ode.js、Ruby、Haskell、Go、Rust以及其他很多語(yǔ)言。現(xiàn)在的開發(fā)者能享受到許多快樂。
至于為什么我這個(gè)Java死忠倒向了Node.js,顯然是因?yàn)槲蚁矚g使用Node.js編程時(shí)的自由感。Java已經(jīng)成為負(fù)擔(dān),而使用Node.js卻沒有這種負(fù)擔(dān)。如果有人雇我寫Java當(dāng)然我還會(huì)接受,因?yàn)槲蚁胭嶅X。
每個(gè)應(yīng)用程序都有真實(shí)的需求。只因?yàn)橄矚gNode.js就一直使用Node.js的態(tài)度顯然不可取,選擇一種語(yǔ)言或框架必然有技術(shù)上的原因。例如,我之前的一些工作涉及了XBRL文檔。由于最好的XBRL庫(kù)是用Python實(shí)現(xiàn)的,因此要完成項(xiàng)目,就必須學(xué)習(xí)Python。
所以,要誠(chéng)實(shí)評(píng)價(jià)真實(shí)需求,然后根據(jù)結(jié)果進(jìn)行選擇。
喜歡小編輕輕點(diǎn)個(gè)關(guān)注吧!