Node.js 流

學習流的作用以及為什麼它們如此重要以及如何使用它們。 什麼是流 為什麼使用流 流的一個例子 pipe() 基於流的 Node.js API 不同類型的流 如何創建可讀流 如何創建可寫流 如何從可讀流中獲取數據 如何將數據發送到可寫流 告訴可寫流您已經結束寫入 什麼是流 流是 Node.js 應用程序的基本概念之一。它們是一種以高效方式處理讀取/寫入文件、網絡通信或任何類型的端到端信息交換的方法。 流不是 Node.js 獨有的概念。它們在數十年前引入了 Unix 操作系統,程序可以通過管道運算符(|)相互交互地傳遞流。 例如, 在傳統的方式中,當您要求程序讀取一個文件時,文件被從頭到尾讀入內存,然後進行處理。 使用流,您可以分塊讀取它,處理其內容,而無需將其全部保留在內存中。 Node.js 的 stream 模塊 提供了所有流 API 的基礎。 為什麼使用流 流主要提供了兩個優勢,與其他數據處理方法相比: 內存效率:您不需要在能夠處理數據之前將大量數據加載到內存中。 時間效率:只要有數據可用,開始處理數據所需的時間遠小於等到整個數據有效負載可用時的時間。 流的一個例子 一個典型的例子是從磁盤讀取文件。 使用 Node fs 模塊,當建立一個新的連接到您的 HTTP 服務器時,您可以讀取一個文件並在 HTTP 上提供它: const http = require('http'); const fs = require('fs'); const server = http.createServer(function (req, res) { fs.readFile(__dirname + '/data.txt', (err, data) => { res.end(data); }); }); server....

Node.js 簡介

這篇文章是關於 Node.js 的入門指南,Node.js 是一個運行在伺服器端的 JavaScript 執行環境。Node.js 是建立在 Google Chrome V8 JavaScript 引擎之上,主要用於建立網頁伺服器,但不僅限於此。 概述 [Node.js 的最佳特性](#node.js 的最佳特性) 快速 簡單 JavaScript V8 異步平台 大量的函式庫 [一個 Node.js 應用程式範例](#一個 Node.js 應用程式範例) [Node.js 框架和工具](#node.js 框架和工具) Node.js 是一個在伺服器上運行的 JavaScript 執行環境。 Node.js 是開源的、跨平台的,自從在2009年推出以來,它已經變得非常流行,現在在網頁開發中扮演著重要的角色。如果以 GitHub 上的 stars 數量作為流行度指標,擁有58000+ stars就意味著非常受歡迎。 Node.js 運行 V8 JavaScript 引擎,該引擎是 Google Chrome 的核心,而 Node.js 可以利用使 Chrome JavaScript 執行時非常高速的工程師的工作,這使得 Node.js 可以受益於巨大的性能改進和即時編譯。由於這一點,運行在 Node.js 中的 JavaScript 代碼可以非常高效。 Node.js 的應用程式由單個進程運行,不為每個請求創建新的線程。Node.js 的標準庫提供了一組異步 I/O 原語,可以防止 JavaScript 代碼阻塞,通常情況下 Node.js 的庫都是使用非阻塞的範例來編寫,使得阻塞行為成為異常而不是正常行為。 當 Node....

Node.js,開發與生產環境的差異

了解如何為生產和開發環境設置不同的配置。 你可以為生產與開發環境設置不同的配置。 Node.js 假設它一直在開發環境中運行。但你可以通過設置 NODE_ENV=production 的環境變量,告訴 Node.js 你正在生產環境中運行。 通常,在 shell 中執行以下命令即可完成此操作: export NODE_ENV=production 但最好將其放在您的 shell 配置文件中(例如使用 Bash shell 的 .bash_profile),以便在系統重新啟動時此設置仍然有效。 你還可以在應用程序初始化命令之前加上環境變量: NODE_ENV=production node app.js 這個環境變量是一個經常在外部庫中使用的約定。 將環境設置為 production 通常可以確保: 日誌保持在最低限度的關鍵級別 融入更多的緩存層以優化性能 例如,Express 使用的模板庫 Pug 如果 NODE_ENV 沒有設置為 production,則會在調試模式下編譯。在開發模式下,每次請求都會編譯 Express 視圖,而在生產模式下則會被緩存。這只是其中一個例子,還有很多其他例子。 Express 提供了特定於環境的配置鉤子,根據 NODE_ENV 變量的值自動調用: app.configure('development', () => { //... }) app.configure('production', () => { //... }) app.configure('production', 'staging', () => { //... }) 例如,你可以使用這個來為不同模式設置不同的錯誤處理程序: app.configure('development', () => { app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); }) app....

Node.js中的錯誤處理

如何在Node.js應用程式執行期間處理錯誤 Node.js中的錯誤通過異常處理。 建立異常 使用throw關鍵字來建立異常: throw value JavaScript執行此行時,正常程式流程將中斷,控制權會返回到最近的異常處理器。 通常在用戶端程式碼中,value可以是任何JavaScript值,包括字串、數字或對象。 在Node.js中,我們不會拋出字串,而是拋出錯誤對象。 錯誤對象 錯誤對象是Error對象的實例,或是擴展了Error類別的對象,這些類別提供在錯誤核心模組中。 throw new Error('Ran out of coffee') 或者 class NotEnoughCoffeeError extends Error { //... } throw new NotEnoughCoffeeError 處理異常 異常處理器是一個try/catch語句。 在try區塊中的代碼行引發的任何異常都在相應的catch區塊中進行處理: try { //代碼行 } catch (e) { } 此示例中的e是異常值。 您可以添加多個處理器,可以捕獲不同類型的錯誤。 捕獲未捕獲的異常 如果在執行程序期間拋出未捕獲的異常,程序將崩潰。 為了解決這個問題,你可以聽取process對象上的uncaughtException事件: process.on('uncaughtException', (err) => { console.error('There was an uncaught error', err) process.exit(1) //根據Node文檔,這是必需的 }) 您無需匯入process核心模組,因為它會自動注入。 使用承諾處理異常 使用承諾,您可以鏈接不同的操作,並在結尾處理錯誤: doSomething1() .then(doSomething2()) .then(doSomething3()) .catch(err => console.error(err)) 您如何知道錯誤發生在哪裡?實際上,您並不知道,但您可以在調用的每個函數(doSomethingX)中處理錯誤,在錯誤處理器內部拋出一個新錯誤,將調用外部的catch處理器: const doSomething1 = () => { //....

Node.js事件循環

事件循環是了解Node.js最重要的方面之一。 為什麼這個很重要?因為它解釋了Node.js為什麼可以是非同步的並且具有非阻塞的I/O,也解釋了Node.js的"殺手級應用",這正是使它變得如此成功的原因。 Node.js的JavaScript代碼運行在單個線程上,一次只能發生一件事。 這實際上是一種很有幫助的限制,因為它簡化了編程,不需要擔心並發問題。 您只需注意如何編寫代碼,避免阻塞線程的任何事情,例如同步網絡調用或無限循環。 總的來說,在大多數瀏覽器中,每個瀏覽器標簽都有一個事件循環,以確保每個進程都是隔離的,避免網頁中的無限循環或重型處理導致整個瀏覽器被阻塞。 環境管理多個並發的事件循環,以處理API調用等。Web Worker也運行在它們自己的事件循環中。 您只需要關心您的代碼將運行在一個單獨的事件循環上,並根據這一點來編寫代碼,以避免阻塞它。 阻塞事件循環 任何需要太長時間才能將控制權交還給事件循環的JavaScript代碼將會阻塞頁面上的任何JavaScript代碼的執行,甚至阻塞UI線程,使用戶無法點擊、滾動頁面等。 在JavaScript中,幾乎所有的I/O原語都是非阻塞的,例如網絡請求、文件系統操作等。阻塞是例外,這就是為什麼JavaScript在很大程度上基於回調,最近則是基於Promise和async/await的原因。 調用堆疊 調用堆疊是一個後進先出(LIFO)的隊列。 事件循環會不斷檢查調用堆疊,以查看是否有需要運行的函數。 在這個過程中,它將找到的每個函數調用添加到調用堆疊中,並按順序執行每個函數。 您可能熟悉調試器或瀏覽器控制台中的錯誤堆疊跟踪,瀏覽器通過查找調用堆疊中的函數名稱來告訴您哪個函數產生了當前的調用,如下圖所示: 一個簡單的事件循環解釋 讓我們舉個例子: const bar = () => console.log('bar') const baz = () => console.log('baz') const foo = () => { console.log('foo') bar() baz() } foo() 這段代碼將打印出: foo bar baz 如預期一樣。 當代碼運行時,首先調用foo(),在foo()內部,我們首先調用bar(),然後調用baz()。 此時,調用堆疊如下: 事件循環在每次迭代時檢查調用堆疊,並執行其中的所有函數,直到調用堆疊為空。 排隊函數的執行 上面的示例看起來很正常,沒有什麼特別之處,JavaScript找到要執行的東西,按順序執行它們。 讓我們來看看如何延遲一個函數,直到堆疊清空。 setTimeout(() => {}, 0)的用法是調用一個函數,但是在代碼中的其他函數執行完畢之後再執行它。 請看下面的例子: const bar = () => console.log('bar') const baz = () => console.log('baz') const foo = () => { console....

Node.js的簡史

回顧Node.js從2009年到現在的歷史 你可能難以置信,但Node.js只有9歲。 相比之下,JavaScript已經有23歲了,而我們所熟悉的網路(在Mosaic問世之後)已經有25歲了。 對於一個技術而言,9年的歷史只是極短暫的時間,但Node.js似乎已經存在了很久很久。 我很榮幸從它剛出生的那天開始就與Node一起工作,儘管當時只有很少的資訊,但你已經能夠感覺到它是一個重大的事物。 在這篇文章中,我想從歷史的角度來概述Node的大局。 一點歷史 2009年 2010年 2011年 2012年 2013年 2014年 2015年 2016年 2017年 2018年 2019年 2020年 一點歷史 JavaScript是一種由Netscape創建的的網頁瀏覽器操控語言,用於操控瀏覽器內部的網頁,Netscape Navigator。 Netscape的業務模式之一是出售Web伺服器,其中包括一個名為Netscape LiveWire的環境,可以使用伺服器端的JavaScript創建動態網頁。因此,伺服器端JavaScript的概念並不是由Node.js引入的,但它的歷史和JavaScript一樣久遠,只是當時並不成功。 導致Node.js崛起的重要因素之一就是時機。幾年來,JavaScript被視為一種認真的語言,多虧了“Web 2.0”應用程序,這些應用程序向世界展示了現代化Web體驗的可能性(比如Google Maps或GMail)。 JavaScript引擎的性能水平大幅提高,多虧於瀏覽器競爭的戰鬥,這場戰鬥至今依然激烈進行中。每個主要瀏覽器背後的開發團隊每天都在努力提供更好的性能,這對於JavaScript作為一個平台來說是個巨大的勝利。Node.js在幕後使用的V8引擎就是其中之一,特別是Chrome的JS引擎。 但當然,Node.js不僅僅因為純粹的運氣或時機而受歡迎。它在JavaScript在服務器端的編程上引入了很多創新思維。 2009年 Node.js誕生 第一個形式的npm誕生 2010年 Express誕生 Socket.io誕生 2011年 npm達到1.0版本 大公司開始採用Node.js:LinkedIn, Uber Hapi誕生 2012年 采用速度非常快 2013年 第一個使用Node.js的大型網站平台:Ghost Koa誕生 2014年 大分叉:io.js是Node.js的一個重大分叉,旨在引入ES6支持並加快發展速度 2015年 Node.js基金會成立 io.js合併回Node.js npm引入私有模組 Node 4(此前曾沒有1、2、3版本的發布) 2016年 leftpad事件 Yarn誕生 Node 6 2017年 npm更加關注安全性 Node 8 - 9 HTTP/2 V8將Node納入測試套件,正式將Node作為JS引擎的目標之一,除Chrome外還有許多JS引擎 每週下載30億個npm套件 2018年 Node 10 - 11 ES模塊 ....

Node事件模組

Node.js的events模組提供了EventEmitter類別 events模組為我們提供了EventEmitter類別,這是在Node中處理事件的關鍵。 我在這篇完整的文章中發布了更多詳細內容,所以在這裡我僅描述API而不提供進一步的用例。 const EventEmitter = require('events') const door = new EventEmitter() 事件監聽器也使用這些事件: newListener 當監聽器被添加時 removeListener 當監聽器被移除時 以下是最常用方法的詳細說明: emitter.addListener() emitter.emit() emitter.eventNames() emitter.getMaxListeners() emitter.listenerCount() emitter.listeners() emitter.off() emitter.on() emitter.once() emitter.prependListener() emitter.prependOnceListener() emitter.removeAllListeners() emitter.removeListener() emitter.setMaxListeners() emitter.addListener() emitter.on()的別名。 emitter.emit() 觸發一個事件。按照它們註冊的順序同步調用每個事件監聽器。 emitter.eventNames() 返回一個字串陣列,表示當前EventListener上註冊的事件: door.eventNames() emitter.getMaxListeners() 獲取可以添加到EventListener對象的最大監聽器數量,默認為10,但可以通過使用setMaxListeners()增加或減少。 door.getMaxListeners() emitter.listenerCount() 獲取傳遞的事件的監聽器的數量: door.listenerCount('open') emitter.listeners() 獲取傳遞的事件的監聽器數組: door.listeners('open') emitter.off() emitter.removeListener()在Node 10中新增的別名。 emitter.on() 添加在事件觸發時調用的回調函數。 用法: door.on('open', () => { console.log('門被打開了') }) emitter.once() 添加在註冊後僅在首次觸發事件時調用的回調函數。這個回調函數只會被調用一次,後續不再調用。 const EventEmitter = require('events') const ee = new EventEmitter() ee....

Node事件發射器

如何在Node中使用自定義事件 如果你在瀏覽器中使用JavaScript,你知道多數的用戶互動是透過事件處理的,像是滑鼠點擊、鍵盤按鍵、對滑鼠移動作出反應等等。 在後端,Node給了我們使用events模組來建立類似的系統的選項。 這個模組中,有一個EventEmitter類別,我們會用它來處理我們的事件。 你可以使用以下語法來初始化一個EventEmitter物件: const EventEmitter = require('events') const eventEmitter = new EventEmitter() 這個物件暴露了許多方法,其中包括on和emit。 emit方法用於觸發一個事件。 on方法用於添加一個當事件觸發時要執行的回調函數。 觸發和監聽事件 舉個例子,我們來創建一個start事件,作為一個示範,我們只是對它作出反應,並將其日誌記錄到控制台: eventEmitter.on('start', () => { console.log('started') }) 當我們執行 eventEmitter.emit('start') 事件處理函數將會被觸發,並且我們將得到控制台日誌記錄。 addListener()是on()的別名,如果你看到那樣的寫法,它們表示的是同一個意思。 將參數傳遞給事件 你可以通過將它們作為額外的參數傳遞給emit()的方式,將參數傳遞給事件處理函數: eventEmitter.on('start', (number) => { console.log(`started ${number}`) }) eventEmitter.emit('start', 23) 多個參數: eventEmitter.on('start', (start, end) => { console.log(`started from ${start} to ${end}`) }) eventEmitter.emit('start', 1, 100) 只監聽一次事件 EventEmitter物件還提供了once()方法,你可以使用它來創建一個一次性事件監聽器。 一旦該事件觸發,該監聽器就停止監聽。 例如: eventEmitter.once('start', () => { console.log(`started!`) }) eventEmitter.emit('start') eventEmitter.emit('start') //不會觸發 刪除事件監聽器 一旦你創建了一個事件監聽器,你可以使用removeListener()方法來刪除它。...

Node緩衝區

瞭解Node緩衝區是什麼,它們用於什麼,以及如何使用它們。 緩衝區是什麼? 緩衝區是一塊內存區域。與C、C++或Go等使用系統編程語言的開發人員(或任何與內存交互的程序員)相比,JavaScript開發人員對這個概念不太熟悉,因為js開發人員並不需要直接與內存交互。 它代表著在V8 JavaScript引擎之外分配的固定大小的內存塊(無法調整大小)。 您可以將緩衝區視為一個整數數組,其中每個整數表示一個字節的數據。 它是由Node的Buffer類實現的。 為什麼我們需要緩衝區? 引入緩衝區是為了幫助開發人員處理二進制數據,在傳統上,js只處理字符串而不是二進制數據。 緩衝區與流(streams)緊密相關。當流處理器接收到的數據速度比它處理的速度快時,它將數據放入緩衝區。 對於緩衝區的一個簡單的可視化是當您觀看YouTube視頻時,紅色條超出了您的視覺點:您正在比您查看數據更快地下載數據,並且您的瀏覽器將其緩衝。 如何創建一個緩衝區 使用Buffer.from(),Buffer.alloc()和Buffer.allocUnsafe()方法來創建緩衝區。 const buf = Buffer.from('Hey!') Buffer.from(array) Buffer.from(arrayBuffer[, byteOffset[, length]]) Buffer.from(buffer) Buffer.from(string[, encoding]) 您還可以通過傳遞大小來初始化緩衝區。這將創建一個1KB大小的緩衝區。 const buf = Buffer.alloc(1024) //或者 const buf = Buffer.allocUnsafe(1024) 使用緩衝區 訪問緩衝區的內容 緩衝區作為字節數組,可以像訪問數組一樣訪問。 const buf = Buffer.from('Hey!') console.log(buf[0]) //72 console.log(buf[1]) //101 console.log(buf[2]) //121 這些數字是緩衝區位置中標識字符的Unicode編碼(H => 72,e => 101,y => 121)。 您可以使用toString()方法打印緩衝區的全部內容。 console.log(buf.toString()) 请注意,如果您使用設置大小的數字初始化緩衝區,您將獲得預初始化內存的訪問權限,其中包含隨機數據,而不是一個空的緩衝區。 獲取緩衝區的長度 使用length屬性。 const buf = Buffer.from('Hey!') console.log(buf.length) 遍歷緩衝區的內容 const buf = Buffer.from('Hey!') for (const item of buf) { console....

Node路徑模組

Node.js的路徑模組提供了一些有用的功能來處理檔案路徑。 path模組提供了許多非常有用的功能來存取和操作檔案系統。 不需要單獨安裝它。由於它是Node的核心部分,只需透過require進行引用即可: const path = require('path') 此模組提供了path.sep來提供路徑分隔符號(在Windows上為\,在Linux和macOS上為/),以及path.delimiter提供路徑分隔符(在Windows上為;,在Linux和macOS上為:)。 以下是path的方法: path.basename() path.dirname() path.extname() path.isAbsolute() path.join() path.normalize() path.parse() path.relative() path.resolve() path.basename() 返回路徑的最後一部分。第二個參數可以過濾掉檔案的副檔名: require('path').basename('/test/something') //something require('path').basename('/test/something.txt') //something.txt require('path').basename('/test/something.txt', '.txt') //something path.dirname() 返回路徑的目錄部分: require('path').dirname('/test/something') // /test require('path').dirname('/test/something/file.txt') // /test/something path.extname() 返回路徑的副檔名部分: require('path').extname('/test/something') // '' require('path').extname('/test/something/file.txt') // '.txt' path.isAbsolute() 如果是絕對路徑則返回true: require('path').isAbsolute('/test/something') // true require('path').isAbsolute('./test/something') // false path.join() 將兩個或多個路徑部分組合起來: const name = 'flavio' require('path').join('/', 'users', name, 'notes.txt') //'/users/flavio/notes.txt' path.normalize() 當路徑包含相對位置符(.或..)或連續的斜線時,嘗試計算實際的路徑: require('path').normalize('/users/flavio/..//test.txt') ///users/test.txt path.parse() 將一個路徑解析為一個包含其組成部分的物件: root:根路徑 dir:從根路徑開始的目錄路徑 base:檔案名稱+副檔名 name:檔案名稱 ext:檔案的副檔名 範例:...