<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                >[info]部分內容來源:《新時期的Node.js入門》- 李鍇 推薦閱讀: [https://www.zhihu.com/question/327657434/answer/715340900](https://www.zhihu.com/question/327657434/answer/715340900) [https://elemefe.github.io/node-interview/#/sections/zh-cn/](https://elemefe.github.io/node-interview/#/sections/zh-cn/) [TOC] # 底層機制 ## 單線程與多線程 其他語言(如 Java、C++等)都有多線程的語言特性,即開發者可以派生出多個線程來協同工作;Node 并沒有提供多線程的機制,開發者無法在一個獨立進程中增加新的線程,但是可以派生出多個進程來達到并行完成工作的目的。 Node 的底層實現(C++)并非是單線程的,libuv 會通過類似線程池的實現來模擬不同操作系統下的異步調用,這對開發者來說是不可見的。 <span style="color: MediumOrchid;font-size: 20px;">Libuv 中的多線程</span> ![](https://box.kancloud.cn/2cd0797877324f39d617015066fb1cb5_637x300.png =450x) Libuv 是一個跨平臺的異步 IO 庫,它結合了 UNIX 下的 libev 和 Windows下的 IOCP 的特性,最早由 Node 的作者開發,專門為 Node 提供多平臺下的異步 IO 支持。Node 中的非阻塞 IO 以及事件循環的底層機制,都是由 libuv 來實現的。 在 Windows 環境下,libuv直接使用 Windows 的 IOCP(I/O Completion Port)來實現異步 IO。在非 Windows 環境下,libuv 使用多線程來模擬異步 IO。 以 readFile 為例,讀取文件的系統調用是由 libuv 來完成的,Node 只負責調用 libuv 的接口,等數據返回后再執行對應的回調方法。 ## 并行與并發 首先搞清楚這兩個概念的差別:這里以一個排隊取火車票的場景來進行介紹 并發(Concurrent):并發是假設有兩個隊伍但只有 1 個取票機,兩個隊伍輪流取票。 并行(Parallel):并行是假設有兩個隊伍,不同的是開放了 2 個取票機,那么這兩個隊列可以同時向前移動。 >以操作系統的角度:并發是指宏觀上在一段時間內能同時運行多個程序,而并行則指同一時刻能運行多個指令。并行需要硬件支持,如多流水線、多核處理器或者分布式計算系統。操作系統通過引入進程和線程,使得程序能夠并發運行。 Node 中的并發:單線程支持高并發,通常是依靠異步+事件循環來實現的,異步使得代碼能在面臨多個請求時不會發生阻塞,事件循環提供了 IO 調用結束后調用回調函數的能力。 >[success]多個請求同時到達時,Java、C++ 等能通過開多個線程來處理請求,而 node.js 卻不能開多線程,其利用事件驅動和異步 I/O 的特性極大地提升了程序性能從而使其有能力這種高并發場景。具體的解釋可以參考《深入淺出 node.js》筆記部分。 ## Node中的事件循環(event loop) 瀏覽器的事件循環中提到了宏任務和微任務,在 node.js 中,事件循環被分成了 6 個不同的階段。**每個階段執行特定的宏任務,每執行完一個階段的回調,就清空一次微任務隊列。** 宏任務包括?`script?, setTimeout?, setInterval?, setImmediate?, I/O?, UI rendering` 微任務包括`?process.nextTick, promise.then, Object.observe?, MutationObserver` [MutationObserver使用方式見這里](http://javascript.ruanyifeng.com/dom/mutationobserver.html) ***** ![](https://box.kancloud.cn/6cd4a0b75719a1aac894564bcfafcb2d_430x411.png) * timers: 用于處理 setTimeout() 和 setInterval() 的回調。 * I/O callbacks: 大多數的回調方法會在這個階段執行,除了 timers、close 和 setImmediate * idle, prepare: 僅內部使用。 * poll: 輪訓,不斷檢查有沒有新的 I/O 事件 * check: 執行 setImmediate() 事件的回調。 * close callbacks: 處理一些 close 相關的事件,例如 socket.on('close',...) <span style="color: MediumOrchid;font-size: 20px;">process.nextTick 與 setImmediate</span> `setImmediate`接受一個回調函數作為參數,其不像 setTimeout 一樣可以設置定時器的時間,事件循環的整個 check 階段是為 setImmediate 方法而設置的:一般情況下,當事件循環到達 poll 階段后,就會檢查當前代碼是否調用了 setImmediate,如果一個回調函數是被 setImmediate 方法調用的,事件循環就會跳出 poll 階段而進入 check 階段。 `process.nextTick`接受一個回調函數作為參數,該方法定義的回調方法會被加入到名為 nextTickQueue 的隊列中,在事件循環的任何階段,如果 nextTickQueue 不為空,都會在當前階段操作結束后優先執行 nextTickQueue 中的回調函數。 >網上有的文章說 process.nextTick 是 idle 觀察者,setImmediate 屬于 check 觀察者(《深入淺出 node.js 》里也是這么說的,不過那也是好幾年前了),這個實際上與 node 的版本有關,一般也不用特別糾結。可以閱讀 [這篇帖子](https://segmentfault.com/q/1010000011914016/a-1020000011915491) >[warning]看上去似乎和微任務的執行是類似的,那么 promise.then 和 process.nextTick 的回調誰更先執行呢?setImmediate 和 nextTick 的出現是為了解決什么問題呢? ```js let promise = new Promise((resolve, reject) => { resolve() }) promise.then(() => { console.log('promise') }) process.nextTick(function () { console.log('nextTick') }) // 打印順序:nextTick promise 無論 Promise 放在之前還是之后都是一樣 ``` 看上去似乎是 nextTick 的回調比 promise.then 的回調優先級更高 ***** TODO - node 事件循環案例 - node 事件循環補充要點 ***** # 常用模塊(原生node.js) ## Buffer Buffer 是 node.js 特有的(區別于瀏覽器 JavaScript)的數據類型,主要用來處理二進制數據。前后端通信時二進制數據流十分常見(例如傳輸一張 gif 圖片)。Buffer 屬于固有(built-in)類型,因此無需 require 引入。 在文件操作和網絡操作中,如果不顯式聲明編碼格式,其返回數據的默認類型就是 Buffer。 **拼接Buffer** ``` var data = [] res.on('data', function (chunk) { data.push(chunk) }) res.on('end', function () { var buf = Buffer.concat(data) console.log(buf.toString()) }) ``` 更多與 Buffer 相關的內容見《深入淺出 node.js》部分 ## 文件系統(File System) 該模塊提供了讀寫文件的能力,借助于底層的 linux 的 C++ API 來實現,這些 API 大多提供同步和異步兩種版本。下面僅介紹幾個常用的。 - `fs.readFile(file[, options], callback)` readFile 方法用于異步讀取文本文件中的內容,適用于體積較小的文件,數百 MB 的文件建議使用 stream。readFile 讀出的數據需要在回調方法中獲取,而 readFileSync 直接返回文本數據內容 ```js const fs = require('fs') fs.readFile('foo.txt', function (err, data) { if (err) throw err console.log(data) }) // 如果不指定readFile的coding配置,其會返回Buffer格式 // <Buffer 48 65 .. .. ..> const data = fs.readFileSync('foo.txt', {encoding: 'UTF-8'}) ``` - `fs.writeFile(file, data[, options], callback)` fs.writeFile('文件路徑','要寫入的內容',['編碼'],'回調函數') 寫入時如果文件不存在,則會嘗試創建它,默認的 flag 為 ’w’,r 代表讀取文件,w 代表寫文件,a 代表追加。 [https://www.cnblogs.com/starof/p/5038300.html](https://www.cnblogs.com/starof/p/5038300.html) ```js fs.writeFile(filePath, mp3, 'binary', err => { if (err) { res.json({ error: 1, msg: '下載失敗' }) } else { res.json({ error: 0, msg: '下載成功', path: downloadUrl }) } }) ``` - `fs.stat(path, callback)` stat 方法通常用于獲取文件的狀態,通常開發者可以在調用 open()、read()、或者 write 方法之前調用 fs.stat 方法,用來判斷文件是否存在 如果文件存在,則返回文件的狀態信息,如下 ```shell Stats { dev: 215266619, mode: 33206, nlink: 1, uid: 0, gid: 0, rdev: 0, blksize: undefined, ino: 2251799814181389, size: 2023, blocks: undefined, atimeMs: 1557839146143.9646, mtimeMs: 1559027178498.3296, ctimeMs: 1559027178498.3296, birthtimeMs: 1557839146143.9646, atime: 2019-05-14T13:05:46.144Z, mtime: 2019-05-28T07:06:18.498Z, ctime: 2019-05-28T07:06:18.498Z, birthtime: 2019-05-14T13:05:46.144Z } ``` 如果文件不存在,則會出現 `Error: ENOENT: no such file or directory` 的錯誤 **實例:獲取目錄下所有的文件名** 這是一個常見的需求,實現這個功能只需要 fs.readdir 以及 fs.stat 兩個 API,readdir 用于獲取目錄下的所有文件或者子目錄,stat 用于判斷具體每條記錄時文件還是子目錄,如果是子目錄,則遞歸調用整個方法 ``` const fs = require('fs') function getAllFileFromPath(path) { fs.readdir(path, function (err, res) { for (let subPath of res) { // 這里使用同步方法而不是異步 let statObj = fs.statSync(path + '/' + subPath) if (statObj.isDirectory()) { // 判斷是否為目錄 console.log('Dir: ', subPath) // 如果是文件夾,遞歸獲取子目錄中的文件列表 } else { console.log('File: ', subPath) } } }) } getAllFileFromPath(__dirname) ``` ## HTTP 服務 HTTP 模塊提供了一系列用于網絡傳輸的 API,這些 API 大都位于比較底層的位置,可以讓開發者自由控制整個 HTTP 傳輸過程 在 HTTP 模塊中,node 定義了一些頂級的類、屬性以及方法,如下所示 ***** Class: http.Agent Class: http.ClientRequest Class: http.Server Class: http.ServerResponse Class: http.IncomingMessage http.METHODS http.STATUS_CODES http.createClient([port], [, host]) http.createServer([requestListener]) http.get(options[, callback]) http.request(options[, callback]) ...... ***** ### 創建 HTTP 服務 ``` const http = require('http') const server = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}) res.end('Hello World!') }) server.listen(3000) ``` 上面的代碼使用 createServer 方法創建了一個簡單的 HTTP 服務器,該方法返回一個 http.server 類的實例,createServer 方法包含了一個匿名的回調函數,該函數有兩個參數 req 和 res,它們分別是 IncomingMessage 和 ServerResponse 的實例。分別表示 HTTP 的 request 和 response 對象,服務器創建完成后,node 進程開始循環監聽 3000 端口(由listen方法實現) ### 處理 HTTP 請求 **request 對象** ***** **1.method、URL** ``` const method = req.method const url = req.url http://example.com/index.html?nam=Lear -> url: /index.html?name=Lear ``` **2.header** http header 通常為以下的形式 ``` { 'content-length': '123', 'content-type': 'text/plain', 'connection': 'keep-alive', 'host': 'mysite.com' ... } ``` 通過 req.headers 獲取一個 JSON 對象,可以對屬性名進行單獨索引 ``` const headers = req.headers const userAgent = headers['user-agent'] ``` **3.request body** node 通過 stream 來處理 HTTP 的請求體,stream 注冊了 data 和 end 兩個事件,可以用如下的代碼來獲取完整的 HTTP 請求體 ``` let body = [] request.on('data', chunk => { body.push(chunk) }).on('end', () => { body = Buffer.concat(body).toString() }) ``` **response對象** ***** **1.設置 response header** 通過`setHeader`方法可以設置 response 的頭部信息 ```js response.setHeader('Content-Type': 'application/json') ``` `setHeader` 方法只能設置 response header 單個屬性的內容,如果想要一次性設置所有的響應頭和狀態碼,可以使用`writeHead`方法 `writeHead` 方法用于定義 HTTP 響應頭,包括狀態碼等一系列屬性 ```js response.writeHead(200, { 'Content-Length': Buffer.byteLength(body), 'Content-Type': 'text/plain' }) ``` 調用該方法后,服務端向客戶端發送 HTTP 響應頭,后面通常會跟著調用 `res.write` 等方法,響應頭不可重復發送;有時開發者并不會顯式調用該方法,調用 `res.end` 方法也會調用`writeHead`方法,此時 statusCode 會自動設置為 200 **2.response body** response 對象是一個 writableStream 實例,可以直接調用 write 方法進行寫入,寫入完成后,再調用 end 方法將該 stream 發送到客戶端 ```js response.write('<html>') response.write('<body>') response.write('<h1>Hello World!</h1>') response.write('</body>') response.write('</htnl>') response.end() // 或者寫成這樣:直接將 response body 作為 end 方法的參數返回 response.end('<html><body><h1>Hello World!</h1></body></html>') ``` **3.response.end** end 方法在每個 HTTP 請求的最后都會被調用,開發者應該調用該方法來結束 HTTP 請求。如果不調用 end 方法,瀏覽器地址欄左邊的叉號會一直存在,表示該請求尚未完成。 end 方法支持一個字符串或者 buffer 作為參數,可以指定在 HTTP 請求的最后返回的數據,該數據會在瀏覽器頁面上顯示出來;如果定義了回調方法,那么會在 end 返回后調用 ``` res.end('Hello node', () => { console.log('http cycle end') }) ``` ### Stream 文件系統提供的 API 有一個重要的問題,就是如果文件過大,一次性地讀或寫顯然是不可行的,Stream 允許我們把較大的數據分批次地進行讀寫。 在 node.js 中,一共有四種基礎的 stream 類型: - Readable:可讀流(for example fs.createReadStream()) - Writable:可寫流(for example fs.createWriteStream()) - Duplex:既可讀,又可寫(for example net.Socket) - Transform:操作寫入的數據,然后讀取結果,通常用于輸入數據和輸出數據不要求匹配的場景,如 zlib.createDeflate() ```js // 復制文件 const fs = require('fs') const path = require('path') const fileName1 = path.resolve(__dirname, 'data.txt') const fileName2 = path.resolve(__dirname, 'data-bak.txt') const readStream = fs.createReadStream(fileName1) const writeStream = fs.createWriteStream(fileName2) readStream.pipe(writeStream) readStream.on('data', chunk => { console.log(chunk) console.log(chunk.toString()) }) readStream.on('end', () => { console.log('copy done') }) ``` ``` // 也可用于http const http = require('http') const fs = require('fs') const path = require('path') const fileName1 = path.resolve(__dirname, 'data.txt') const server = http.createServer((req, res) => { if (req.method === 'GET') { const readStream = fs.createReadStream(fileName1) readStream.pipe(res) } }) ``` Writable Stream 主要使用 write 方法來寫入數據,該方法是異步的,假設我們創建一個可讀流讀取一個較大的文件,再調用 pipe 方法將數據通過一個可寫流寫入另一個位置。如果讀取的速度大于寫入的速度,那么 node 將會在內存中緩存這些數據。 當然緩沖區也是有大小限制的(state.highWatermark),當達到閾值后,write方法會返回 false,可讀流也進入暫停狀態,當 writable stream 將緩沖區清空之后,會觸發 drain 事件,上游的 readable 重新開始讀取數據 pipe 方法使得數據可以通過管道**由可讀流流入可寫流**。pipe 方法接收一個 writable 對象,當 readable 對象調用 pipe 方法時,會在內部調用 writable 對象的 write 方法進行寫入。 ### ReadLine ReadLine 是一個 node 原生模塊,提供了按行讀取 Stream 中數據的功能 ``` const readline = require('readline') const fs = require('fs') const rl = readline.createInterface({ input: fs.createReadStream('data.txt') }) // 每讀取一行數據出發 rl.on('line', data => { console.log(data) }) rl.on('close', () => { console.log('closed') }) ``` ### Events node 程序中的對象會產生一系列的事件,例如一個 HTTP Server 會在每次有新連接時觸發一個事件,一個 Readable Stream 會在文件打開時觸發一個事件等。所有能觸發事件的對象都是 EventEmitter 類的實例。下面的代碼演示了如何注冊一個事件并觸發它: ```js const eventEmitter = require('events') const myEmitter = new eventEmitter() myEmitter.on('begin', () => { console.log('begin') }) myEmitter.emit('begin') ``` 在實際的開發中,通常不會直接使用 Event 模塊來進行事件處理,而是選擇將其作為基類進行繼承的方式來使用 Event,在 node 的內部實現中,凡是提供了事件機制的模塊,都會在內部繼承 Event 模塊,以 fs 模塊為例,下面是其源碼中的一部分 ``` function FSWatcher() { EventEmitter.call(this) // ...... } util.inherits(FSWatcher, EventEmitter) // util.inherits 是用來繼承的方法 ``` 假設我們要用 node 來開發一個網頁上的音樂播放器,關于播放和暫停的處理,就可以考慮通過繼承 Events 模塊來實現: ``` const util = require('util') const event = require('events') function Player() { event.call(this) } util.inherits(Player, event) const player = new Player() player.on('pause', () => { console.log('pause') }) player.on('play', () => { console.log('playing') }) player.emit('play') // playing ``` ### path 模塊 原文鏈接:[https://www.jianshu.com/p/78fadd20ee61](https://www.jianshu.com/p/78fadd20ee61) 1.連接路徑:path.join(\[path1\]\[, path2\]\[, ...\]) path.join() 方法可以連接任意多個路徑字符串。要連接的多個路徑可做為參數傳入。path.join() 方法在接邊路徑的同時也會對路徑進行規范化。例如: ```js var path = require('path') // 合法的字符串連接 path.join('/foo', 'bar', 'baz/asdf', 'quux', '..') // 連接后 '/foo/bar/baz/asdf' //不合法的字符串將拋出異常 path.join('foo', {}, 'bar') // 拋出的異常 TypeError: Arguments to path.join must be strings' ``` 2.路徑解析:path.resolve(\[from ...\], to) path.resolve() 方法可以將多個路徑解析為一個規范化的絕對路徑。其處理方式類似于對這些路徑逐一進行 cd 操作,與 cd 操作不同的是,這些路徑可以是文件,并且可不必實際存在(resolve() 方法不會利用底層的文件系統判斷路徑是否存在,而只是進行路徑字符串操作)。例如: ```js path.resolve('foo/bar', '/tmp/file/', '..', 'a/../subfile') ``` 相當于 ```shell cd foo/bar cd /tmp/file/ cd .. cd a/../subfile pwd ``` 示例: ```js path.resolve('/foo/bar', './baz') // 輸出結果為 '/foo/bar/baz' path.resolve('/foo/bar', '/tmp/file/') // 輸出結果為 '/tmp/file' path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif') // 當前的工作路徑是 /home/itbilu/node,則輸出結果為 '/home/itbilu/node/wwwroot/static_files/gif/image.gif' ``` 3.對比 ```js const path = require('path'); let myPath = path.join(__dirname,'/img/so'); let myPath2 = path.join(__dirname,'./img/so'); let myPath3 = path.resolve(__dirname,'/img/so'); let myPath4 = path.resolve(__dirname,'./img/so'); console.log(__dirname); // D:\myProgram\test console.log(myPath); // D:\myProgram\test\img\so console.log(myPath2); // D:\myProgram\test\img\so console.log(myPath3); // D:\img\so console.log(myPath4); // D:\myProgram\test\img\so ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看