官术网_书友最值得收藏!

  • 深入淺出Node.js
  • 樸靈
  • 2651字
  • 2020-05-06 16:52:10

1.4 Node的特點

作為后端JavaScript的運行平臺,Node保留了前端瀏覽器JavaScript中那些熟悉的接口,沒有改寫語言本身的任何特性,依舊基于作用域和原型鏈,區別在于它將前端中廣泛運用的思想遷移到了服務器端。下面我們來看看Node相較其他語言的一些特點。

1.4.1 異步I/O

關于異步I/O,向前端工程師解釋起來或許會容易一些,因為發起Ajax調用對于前端工程師而言是再熟悉不過的場景了。下面的代碼用于發起一個Ajax請求:

        $.post('/url', {title: ’深入淺出Node.js'}, function (data) {
          console.log(’收到響應’);
        });
        console.log(’發送Ajax結束’);

熟悉異步的用戶必然知道,“收到響應”是在“發送Ajax結束”之后輸出的。在調用$.post()后,后續代碼是被立即執行的,而“收到響應”的執行時間是不被預期的。我們只知道它將在這個異步請求結束后執行,但并不知道具體的時間點。異步調用中對于結果值的捕獲是符合“Don't call me, I will call you”的原則的,這也是注重結果,不關心過程的一種表現。圖1-2是一個經典的Ajax調用。

圖1-2 經典的Ajax調用

在Node中,異步I/O也很常見。以讀取文件為例,我們可以看到它與前端Ajax調用的方式是極其類似的:

        var fs = require('fs');

        fs.readFile('/path', function (err, file) {
          console.log(’讀取文件完成’)
        });
        console.log(’發起讀取文件’);

這里的“發起讀取文件”是在“讀取文件完成”之前輸出的。同樣,“讀取文件完成”的執行也取決于讀取文件的異步調用何時結束。圖1-3是一個經典的異步調用。

圖1-3 經典的異步調用

在Node中,絕大多數的操作都以異步的方式進行調用。Ryan Dahl排除萬難,在底層構建了很多異步I/O的API,從文件讀取到網絡請求等,均是如此。這樣的意義在于,在Node中,我們可以從語言層面很自然地進行并行I/O操作。每個調用之間無須等待之前的I/O調用結束。在編程模型上可以極大提升效率。

下面的兩個文件讀取任務的耗時取決于最慢的那個文件讀取的耗時:

        fs.readFile('/path1', function (err, file) {
          console.log(’讀取文件1完成’);
        });
        fs.readFile('/path2', function (err, file) {
          console.log(’讀取文件2完成’);
        });

而對于同步I/O而言,它們的耗時是兩個任務的耗時之和。這里異步帶來的優勢是顯而易見的。

關于異步I/O是如何提升效率的及其本身的機制和實現,我們將在第3章中詳述。

1.4.2 事件與回調函數

隨著Web 2.0時代的到來,JavaScript在前端擔任了更多的職責,事件也得到了廣泛的應用。Node不像Rhino那樣受Java的影響很大,而是將前端瀏覽器中應用廣泛且成熟的事件引入后端,配合異步I/O,將事件點暴露給業務邏輯。

下面的例子展示的是Ajax異步提交的服務器端處理過程。Node創建一個Web服務器,并偵聽8080端口。對于服務器,我們為其綁定了request事件,對于請求對象,我們為其綁定了data事件和end事件:

        var http = require('http');
        var querystring = require('querystring');

        // 偵聽服務器的request事件
        http.createServer(function (req, res) {
          var postData = '';
          req.setEncoding('utf8');
          // 偵聽請求的data事件
          req.on('data', function (chunk) {
            postData += chunk;
          });
          // 偵聽請求的end事件
          req.on('end', function () {
            res.end(postData);
          });
        }).listen(8080);
        console.log(’服務器啟動完成’);

相應地,我們在前端為Ajax請求綁定了success事件,在發出請求后,只需關心請求成功時執行相應的業務邏輯即可,相關代碼如下:

        $.ajax({
          'url': '/url',
          'method': 'POST',
          'data': {},
          'success': function (data) {
            // success事件
          }
        });

相比之下,無論在前端還是后端,事件都是常用的。對于其他語言來說,這種俯拾皆是JavaScript的熟悉感覺是基本不會出現的。

事件的編程方式具有輕量級、松耦合、只關注事務點等優勢,但是在多個異步任務的場景下,事件與事件之間各自獨立,如何協作是一個問題。

從前面可以看到,回調函數無處不在。這是因為在JavaScript中,我們將函數作為第一等公民來對待,可以將函數作為對象傳遞給方法作為實參進行調用。

與其他的Web后端編程語言相比,Node除了異步和事件外,回調函數是一大特色??v觀下來,回調函數也是最好的接受異步調用返回數據的方式。但是這種編程方式對于很多習慣同步思路編程的人來說,也許是十分不習慣的。代碼的編寫順序與執行順序并無關系,這對他們可能造成閱讀上的障礙。在流程控制方面,因為穿插了異步方法和回調函數,與常規的同步方式相比,變得不那么一目了然了。

在轉變為異步編程思維后,通過對業務的劃分和對事件的提煉,在流程控制方面處理業務的復雜度與同步方式實際上是一致的。

關于流程控制和事件協作的方法和技巧,我們將在第4章中進一步探討。

1.4.3 單線程

Node保持了JavaScript在瀏覽器中單線程的特點。而且在Node中,JavaScript與其余線程是無法共享任何狀態的。單線程的最大好處是不用像多線程編程那樣處處在意狀態的同步問題,這里沒有死鎖的存在,也沒有線程上下文交換所帶來的性能上的開銷。

同樣,單線程也有它自身的弱點,這些弱點是學習Node的過程中必須要面對的。積極面對這些弱點,可以享受到Node帶來的好處,也能避免潛在的問題,使其得以高效利用。單線程的弱點具體有以下3方面。

? 無法利用多核CPU。

? 錯誤會引起整個應用退出,應用的健壯性值得考驗。

? 大量計算占用CPU導致無法繼續調用異步I/O。

像瀏覽器中JavaScript與UI共用一個線程一樣,JavaScript長時間執行會導致UI的渲染和響應被中斷。在Node中,長時間的CPU占用也會導致后續的異步I/O發不出調用,已完成的異步I/O的回調函數也會得不到及時執行。

最早解決這種大計算量問題的方案是Google公司開發的Gears。它啟用一個完全獨立的進程,將需要計算的程序發送給這個進程,在結果得出后,通過事件將結果傳遞回來。這個模型將計算量分發到其他進程上,以此來降低運算造成阻塞的幾率。后來,HTML5定制了Web Workers的標準,Google放棄了Gears,全力支持Web Workers。Web Workers能夠創建工作線程來進行計算,以解決JavaScript大計算阻塞UI渲染的問題。工作線程為了不阻塞主線程,通過消息傳遞的方式來傳遞運行結果,這也使得工作線程不能訪問到主線程中的UI。

Node采用了與Web Workers相同的思路來解決單線程中大計算量的問題:child_process。

子進程的出現,意味著Node可以從容地應對單線程在健壯性和無法利用多核CPU方面的問題。通過將計算分發到各個子進程,可以將大量計算分解掉,然后再通過進程之間的事件消息來傳遞結果,這可以很好地保持應用模型的簡單和低依賴。通過Master-Worker的管理方式,也可以很好地管理各個工作進程,以達到更高的健壯性。

關于如何通過子進程來充分利用硬件資源和提升應用的健壯性,這是一個值得探究的話題。怎樣才能使我們既享受到無憂無慮的單線程編程,又高效利用資源呢?請挪步到第9章。

1.4.4 跨平臺

起初,Node只可以在Linux平臺上運行。如果想在Windows平臺上學習和使用Node,則必須通過Cygwin或者MinGW。隨著Node的發展,微軟注意到了它的存在,并投入了一個團隊幫助Node實現Windows平臺的兼容,在v0.6.0版本發布時,Node已經能夠直接在Windows平臺上運行了。圖1-4是Node基于libuv實現跨平臺的架構示意圖。

圖1-4 Node基于libuv實現跨平臺的架構示意圖

兼容Windows和*nix平臺主要得益于Node在架構層面的改動,它在操作系統與Node上層模塊系統之間構建了一層平臺層架構,即libuv。目前,libuv已經成為許多系統實現跨平臺的基礎組件。關于libuv的設計,我們將在第3章中介紹。

通過良好的架構,Node的第三方C++模塊也可以借助libuv實現跨平臺。目前,除了沒有保持更新的C++模塊外,大部分C++模塊都能實現跨平臺的兼容。

主站蜘蛛池模板: 东兰县| 亚东县| 伊金霍洛旗| 南丹县| 扬州市| 达日县| 丹阳市| 剑阁县| 手游| 高要市| 独山县| 彰化县| 青阳县| 樟树市| 泾川县| 彰武县| 扶余县| 利津县| 湖北省| 乌鲁木齐县| 繁峙县| 乡宁县| 门头沟区| 甘肃省| 德昌县| 云安县| 淮阳县| 临西县| 渭南市| 筠连县| 东阿县| 交城县| 栾川县| 黎城县| 海盐县| 东城区| 云浮市| 沙河市| 伊川县| 望谟县| 交城县|