node.js高階程式設計閱讀筆記

merloly發表於2019-03-25
  1. 事件驅動程式設計是指程式的執行流程取決於事件,當感興趣的事情發生時由系統呼叫的函式來取代應用返回值的程式設計風格被稱為事件驅動程式設計或者非同步程式設計,它是Node的顯著特徵之一,這種程式設計風格意味著當前程式在處理I/O操作時不會發生阻塞,因此多個I/O操作可以並行進行,當每個操作結束時,將會分別呼叫其對應的回撥函式,執行緒是一種輕量級的程式。
  2. 事件驅動程式設計風格和事件迴圈相伴相生,事件迴圈是一個處於不間斷迴圈的結構,該結構主要有兩種功能,事件檢測和事件觸發處理。事件迴圈只是在一個程式中執行的單個執行緒,這意味著當事件發生時,可以不用中斷就執行事件處理程式。有以下兩個特點:
  • 在任意時刻,最多執行一個事件處理程式。
  • 事件處理程式可以不間斷地執行直到結束。
  1. js的第一類函式(first-class func),函式可以賦值給變數。
(function(){
    var clickCount = 0;
    $('#mybutton').click(function(){
        clickCount ++;
        alert("a");
    })
}())
複製程式碼

閉包可以避免汙染全域性作用域

  1. 在js中,函式操作並不是孤立地工作而是會記住它被宣告時所在的上下文,這能讓函式操作其宣告時所在的上下文以及父上下文中的所有變數。即使宣告回撥函式的那個作用域已經返回,回撥函式依然可以操作該作用域和父作用域中的任意變數。
  2. js是一門強大的語言,因為它具有第一類函式和閉包,所以很適合事件驅動程式設計風格。

載入模組

var module = require('module_name');
複製程式碼

上面的程式碼會匯入一個核心模組或者由npm安裝的模組,require函式會返回一個物件,該物件表示模組對外暴露Js API。根據模組的不同,該物件可能是任意JavaScript值--可以是一個函式,也可以是一個具有若干屬性的物件,屬性可能是函式、陣列或其他任何型別的javascript物件。

  1. 在Node中,檔案和模組是一一對應的
    function r_squared() {
        return Math.pow(r,2);
    }
    function area() {
        Math.PI * r_squared()
    }
    return {
        area: area
    }
}
複製程式碼

module是一個變數,它表示當前模組自身,而module.exports表示模組向需要它的指令碼所匯出的物件,它可以是任意物件。

載入核心模組

node中有一些以二進位制形式釋出的模組,這些模組被稱為核心模組,核心模組只能通過模組名引用,不能通過檔案路徑引用,即使已經存在 一個與其同名的第三方模組,也會優先載入核心模組。 var http = require('http'); 2. 載入檔案模組可以用絕對路徑和相對路徑,還可以使用資料夾路徑來載入模組 var myModule = require('./myModuleDir'); 如此一來,Node就會在指定的資料夾下查詢模組,Node會假定該資料夾是一個包,並試圖查詢包定義,包定義包含在名為package.json的檔案中。如果資料夾中不存在包定義檔案package.json,那麼包的入口點會假定為預設值index.js。反之,如果存在package.json檔案, 那麼Node就會嘗試解析該檔案並查詢main屬性,將main屬性當作入口點的相對路徑。

從node_modules夾載入

如果一個模組名既不是相對路徑,也不是核心模組,那麼Node就會嘗試在當前目錄下的node_modules資料夾中查詢該模組。

快取模組

模組在首次載入時會被快取起來,這意味著如果模組名能被解析為相同的檔名,那麼每次呼叫require('myModule')都會確切的返回 同一模組。

總結

Node取消了js預設的全域性名稱空間,而用CommonJS模組系統取而代之,這樣可以讓你更好地組織程式碼,也因此避免一些安全性問題和錯誤 。可以使用require()函式從檔案或者資料夾載入核心模組、第三方模組或者自定義模組。

名稱空間

名稱空間(namespace)有的語言稱package,是一個在靜態語言中常見的概念,js並不支援原生的名稱空間,可以建立物件字面量來模擬名稱空間。

應用緩衝區處理、編碼和解碼二進位制資料

js善於出來字串,但由於它最初是被設計用來出來HTML文件的,它並不擅長處理二進位制資料。為了使這類二進位制資料處理任務變得容易些 Node引入了一個二進位制緩衝區實現,該實現以Buffer偽類中的js api形式暴露給外界。

建立緩衝區

var buf = new Buffer('hello world'); // 預設為utf-8
var buf = new Buffer('8b76de713ce', 'base64');
var buf = new Buffer(1024)
複製程式碼

在緩衝區中獲取和設定資料

console.log(buf[10]);
複製程式碼

當建立一個已經被初始化的緩衝區是,該緩衝區中包含的資料並非是0,而是一些隨機值。

var buf = new Buffer(1024);
console.log("buf[100]") // -> 5(某個隨機值)
複製程式碼

切分緩衝區

一旦建立或獲取一個緩衝區,可以通過指定開始位置和介紹位置來切分緩衝區,從而建立一個更小的緩衝區

var buffer = new Buffer("this is the content of my buffer");
var smallerBuffer = buffer.slice(8,19);
console.log(smallerBuffer.toString()); // -> "the content"
複製程式碼

複製緩衝區

可以使用copy方法將緩衝區中的一部分複製到另一個緩衝區中

var buffer1 = new Buffer("this is the content of my buffer");
var buffer2 = new Buffer(11);
var targetStart = 0;
var sourceStart = 8;
var sourceEnd = 19;
buffer1.copy(buffer2,targetStart, sourceStart, sourceEnd);
複製程式碼

將源緩衝區的第8到第19個位置上的資料複製到目標緩衝區的起始位置。

緩衝區解碼

var str = buf.toString();
var b64Str = buf.toString("base64");
複製程式碼

小結

有時候你不得不對二進位制資料進行處理,native JS沒提供方法,Node中的Buffer類對訪問連續記憶體塊的操作進行了封裝,可以處理記憶體中的資料 以及切分緩衝區,還可以在兩個緩衝區之間進行記憶體複製。

使用事件發射器模式簡化事件繫結

理解標準回撥模式

後續傳遞風格:一種程式的流程控制權以後繼的形式被顯式傳遞的程式設計風格。

var fs = require('fs');
fs.readFile('/etc/passwd',function(err,fileContent){
  if(err){
  throw err;
  }
  console.log('file content',fileContent.toString());
})
複製程式碼

在這個例子中,將一個內聯匿名函式傳遞給fs.readFile函式。作為其第二個引數,其實這就是在使用CPS(continuation-passing style),因為 在這個內聯匿名函式將函式執行繼續了下去。

理解事件發射器模式

在標準回撥模式中,將一個回撥函式作為引數傳遞給將被執行的函式,這種模式在函式執行結束後需要通知客戶端的情況下工作得很好。但是如果在函式執行過程中發生多個事件或者事件反覆發生了多次,那這種模式就工作得不那麼好了。如果要求在套接字上每當的有可用資料時都能收到通知,標準回撥模式就幫不上什麼忙了,此時正是事件發射器模式派上用場的時候了。

  • 在使用事件發生器模式時,會涉及兩個或者更多的物件,這些物件包括事件發射器以及一個或者多個事件監聽器,一對多的關係。
  • 顧名思義,事件發射器就是可以發射事件的物件,而事件監聽器則是繫結到事件發射器上的程式碼,負責監聽特定型別的事件。
var req = http.request(options, function(response) {
    response.on("data", function(data) {
        console.log("some data from the response", data);
    });
    response.on("end", function() {
    console.log("response ended");
});
});
req.end();
複製程式碼

在上面的例子中,可以看到幾個必要的步驟,Node的http.request API建立了一個HTTP請求,並將其傳送到遠端伺服器。第一行用到了後繼傳遞風 格,傳遞了一個當HTTP伺服器響應時就會被呼叫的行內函數。 一般而言,當需要在請求的操作完成後重新獲取控制權時就使用CPS(Continuation Passing Style)模式,當事件可能發生多次時就使用事件發射器模式。

理解事件型別

請注意發射的事件具有型別,型別用一個字串表示,前面例子中有“data”和“end”兩種事件型別,它們都是由事件發射器定義的任意字串,不過 約定俗成的是,命名通常都是由不包含空格的小寫單片語成。 你無法通過程式設計來判斷事件發射器到底能夠發射哪些型別的事件,因為事件發射器API沒有提供內省機制,因此用到的api應該有文件來記錄它能發射的事件型別。 一旦有相關事件發生,事件發射器就會呼叫相應的事件監聽器,並將相關資料作為引數傳遞給事件監聽器。 下面程式碼模擬了一個能發射兩種型別事件的事件發射器:

var em = new (require(‘events’).EventEmitter)();
em.emit('event1');
em.emit('error',new Error('My mistake'));
複製程式碼

隨便發射一個名為“event1”的事件沒有任何效果,但是在發射“error”事件時,錯誤就會被丟擲到堆疊。如果程式不是執行在REPL命令列環境中, 就會因為這個未捕獲到的錯誤而掛起。一般而言,應該總是監聽錯誤事件,並對其進行恰當的處理。

應用事件發生器API

任何實現了事件發射器模式的物件(比如TCP套接字、HTTP請求等),都實現瞭如下所示的一組方法:

  • .addListenr和.on —— 為指定型別的事件新增事件監聽器。
  • .once —— 為指定型別事件繫結一個僅會被呼叫一次的事件監聽器。
  • .removeEventListener —— 刪除繫結到指定事件上的某個指定事件監聽器
  • .removeAllEventListener —— 刪除繫結到指定事件上的所有事件監聽器。
使用.addListener()或.on()繫結回撥函式

通過指定事件型別和回撥函式,就可以註冊當事件發生時所要呼叫的操作。例如,當有可以用的資料塊時,檔案可讀就會發射“data”事件,下面的程式碼展示如何通過一個回撥函式來通知發生了“data”事件:

function receiveData(data) {
    console.log("got data from file read stream: %j", data);
}
readStream.addListener("data", receiveData);
複製程式碼

還可以用函式.on來代替.addListener函式,它只是.addListen函式的簡寫形式。

繫結多個事件監聽器

事件發射器模式允許多個事件監聽器監聽同一事件發射器的同一型別的事件:

readStream.on("data", function(data){
  console.log('i have some data here');  
})
readStream.on("data", function(data){
  console.log('i have some data here too');  
})
複製程式碼

在上面的程式碼中,readStream物件的“data”型別事件上繫結了兩個函式,每當readStream物件發射“data”事件時,就會看到輸出資訊:

I have some data here.
I have some data here too.
複製程式碼

根據事件型別,事件發射器負責按照事件所繫結的監聽器的註冊順序依次呼叫事件監聽器,這意味著以下兩件事:

  • 某個事件監聽器也許並不會在事件發射之後立即被呼叫,也許在它之前會有別的事件監聽器被呼叫。
  • 異常被丟擲到堆疊並不正常,它通常是由程式碼中的錯誤引起的。當事件被髮射時,如果其中某個事件監聽器在被呼叫時丟擲錯誤,可能會導致一些事件監聽器永遠都不會被呼叫,在這種情況下,事件發射器將會捕捉到錯誤,也許還會處理它。

相關文章