15 個常見的 Node.js 面試問題及答案

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"臨陣磨槍,不快也光。對於成功的編程面試來說,準備和知識面一樣重要。準備使你有信心參加面試,而不用擔心莫名的緊張情緒。如果第一次參加編程面試,這一點尤其重要。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲幫助 Node.js 開發人員更好的面試,我列出了 15 個常見的 Node.js 和網絡開發相關的面試問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在本文中,我們將重點討論 Node.js 相關問題。但是,請記住 JavaScript 問題在 Node.js 面試中也經常問到,所以準備一些對你來說沒什麼壞處。不久前我們寫了一篇關於常見 JavaScript 面試問題的"},{"type":"link","attrs":{"href":"https:\/\/livecodestream.dev\/post\/20-common-javascript-interview-questions\/","title":"xxx","type":null},"content":[{"type":"text","text":"帖子"}]},{"type":"text","text":",涵蓋了所有這些基礎。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在,讓我們深入瞭解面試中可能會問到的 Node.js 問題。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1. Node.js 與 JavaScript 有什麼不同?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/wechat\/images\/2e\/2ee2f07e9bce4fc9778bf05273645365.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2. 什麼時候用 Node.js?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Node.js 是異步的、事件驅動的、非阻塞的和單線程的,使得它成爲開發下面應用程序的完美候選:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實時應用程序,如聊天和提供實時更新的應用程序"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將視頻或其他多媒體內容流式傳輸給大量觀衆的流式應用程序"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其他 I\/O 密集型應用程序,如協作平臺"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"遵循微服務架構的網絡後端"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然而,Node.js 的特性使得它對於其他類型的應用程序來說不是一個理想的選擇。執行 CPU 密集型任務的應用程序(如複雜的數學計算)在使用 CPU 時表現不佳,因爲 Node.js 是單線程的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你想了解更多這方面的信息,請查看我們的文章 Node.js 架構以及何時在項目中使用。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3. EventEmitter 做了什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Node.js 中任何對象發出的事件都是 EventEmitter 類的實例,就像 http 模塊。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所有 EventEmitter 類都可以使用 eventEmitter.on() 函數將事件偵聽器附加到事件。然後一旦捕捉到這樣的事件,就會同步地逐個調用它的偵聽器。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"null"},"content":[{"type":"text","text":"const events = require(\"events\");const eventEmitter = new events.EventEmitter();const eventListener = function(){ console.log(\"event triggered\");}eventEmitter.on(\"emitted\", eventListener);eventEmitter.emit(\"emitted\");\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"4. 事件循環是什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單線程的 Node.js 必須是非阻塞的,以防止線程阻塞在需要很長時間才能完成的任務上,事件循環負責實現這種非阻塞行爲,它使用應用程序線程調度掛起的任務。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Node.js 在任務完成時通過回調來處理異步函數返回的響應。與創建任務的事件類似,任務完成後也會發出一個事件。Node.js 將需要處理的事件添加到事件隊列。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事件循環對事件隊列中的事件進行迭代,並安排何時執行其關聯的回調函數。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"5. 流是什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Stream 流是從源讀取或寫入數據並將其傳輸到連續流目標的管道。有四種類型:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可讀"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可寫的"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可讀寫"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先寫入,再讀出來"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個流也是一個 EventEmitter。這意味着流對象可以在流上沒有數據、流上有可用數據或流中的數據在程序刷新時發出事件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"null"},"content":[{"type":"text","text":"const fs = require(\"fs\");const readableStream = fs.createReadStream(\"test.txt\");let content = \"\";readableStream.on(\"data\", (chunk) => { content += chunk;});readableStream.on(\"end\", () => { console.log(content);});\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"6. readFile 和 createReadStream 函數有什麼區別?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"readFile 函數異步讀取文件的全部內容,並存儲在內存中,然後再傳遞給用戶。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"createReadStream 使用一個可讀的流,逐塊讀取文件,而不是全部存儲在內存中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"與 readFile 相比,createReadStream 使用更少的內存和更快的速度來優化文件讀取操作。如果文件相當大,用戶不必等待很長時間直到讀取整個內容,因爲讀取時會先向用戶發送小塊內容。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"null"},"content":[{"type":"text","text":"const fs = require(\"fs\");fs.readFile(\"test.txt\", (err, content) => { console.log(content);});\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"7. 如何處理 Node.js 中未捕獲的異常?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們可以在進程級別捕獲應用程序中未捕獲的異常。爲此將偵聽器附加到 process 全局對象:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"null"},"content":[{"type":"text","text":"process.on(\"uncaughtException\", (err) => { console.log(\"exception caught: \", err);});\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"8. Node.js 能否充分利用多核處理器?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(默認的)Node.js 應用程序總是單線程的,即使在多核處理器上運行,應用程序也能只使用一個處理器。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是 Node.js 的核心模塊之一 Cluster 支持 Node.js 應用程序開啓多核,允許我們創建多個工作進程,這些進程可以在多個內核上並行運行,並共享一個端口來偵聽事件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個進程使用 IPC 與主線程通信,並根據需要將服務器句柄傳遞給其他進程。主進程可以偵聽端口本身並以循環方式將每個新連接傳遞給子進程,也可以將端口分配給子進程以便子進程偵聽請求。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"9. 反應堆設計模式是什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"反應堆設計模式是,Node.js 將回調函數(處理程序)附加到每個 I\/O 操作,然後創建請求時將處理程序提交給解複用器。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"解複用器收集應用程序中發出的每個 I\/O 請求,並將它們作爲隊列中的事件進行排隊。這個隊列就是我們所說的事件隊列。將事件排隊後,解複用器返回應用程序線程的控制。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同時,事件循環遍歷事件隊列中的每個事件,並調用附加的回調來處理事件響應。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這就是 Node.js 中所使用的反應堆模式。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"10. 單線程與多線程網絡後端相比有哪些好處?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"儘管 Node.js 是單線程的,但是大多數用於後端開發的編程語言都提供多線程來處理應用程序操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼單線程有利於後端開發?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"開發人員更容易實現應用程序。我們的應用程序在生產過程中不會突然遇到意外的競爭條件。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單線程應用程序易於擴展。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它們可以毫不延遲地在一個時刻收到的大量用戶請求提供服務。相比之下,當流量較大時,多線程後端必須等待線程池中的線程釋放,才能爲用戶請求提供服務。利用 Node.js 的非阻塞特性,用戶請求不會在單個線程上掛起太長時間(只有在操作不是 CPU 密集型時)。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"11. REPL 是什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"REPL 代表 Read Eval Print Loop,是一個虛擬環境,可以在其中輕鬆地運行編程語言。Node.js 帶有一個內置的 REPL 來運行 JavaScript 代碼,類似於我們在瀏覽器中用來運行 JavaScript 代碼的控制檯。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要啓動 Node.js REPL,只需在命令行上運行 node,然後寫一行 JavaScript 代碼,就可以在下一行看到它的輸出。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"12. process.nextTick 和 setImmediate 有什麼區別?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"傳遞給 setImmediate 函數的回調將在事件隊列上的下一次迭代中執行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另一方面,回調傳遞給 process.nextTick 在下一次迭代之前以及程序中當前運行的操作完成之後執行。在應用程序啓動時,開始遍歷事件隊列之前調用它的回調。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此,回調 process.nextTick 總是在 setImmediate 之前調用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面代碼段:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"null"},"content":[{"type":"text","text":"setImmediate(() => { console.log(\"first\");})process.nextTick(() => { console.log(\"second\");})console.log(\"third\");\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將按順序輸出:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"null"},"content":[{"type":"text","text":"thirdsecondfirst\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"13. stub 是什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"測試應用程序時使用 stub,模擬給定組件或模塊的行爲,你可以將精力集中在要測試的代碼部分。通過使用 stub 代替與測試無關的組件,不必擔心外部組件會影響結果。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如,如果正在測試的組件在預期測試的部分之前有一個文件讀取操作,則可以使用 stub 來模擬該行爲並返回模擬內容,而不用實際讀取文件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 Node.js 中,我們使用像 Sinon 這樣的庫來實現(譯者注,Sinon 在測試中替換某部分代碼,減少測試項編寫的複雜度 "},{"type":"link","attrs":{"href":"https:\/\/sinonjs.org","title":"","type":null},"content":[{"type":"text","text":"https:\/\/sinonjs.org"}]},{"type":"text","text":")。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"14. 爲什麼在 express 中分離“應用程序”和“服務器”是一種好的做法?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過在 Express 中分離應用程序和服務器,可以將 API 實現與網絡相關配置分開。在不執行網絡調用的情況下執行 API 測試,保證了更快的測試執行和更好的代碼覆蓋度量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要實現這種分離,應該在單獨的文件中聲明 API 和 server,對應 app.js 和 server.js:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"null"},"content":[{"type":"text","text":"\/\/ app.jsconst express = require(\"express\");const app = express();app.use(\"\/\", index);app.use(\"\/contact\", contact);app.use(\"\/user\", user);module.exports = app;\/\/ server.jsconst http = require(\"http\");const app = require(\"\/app\");app.set('port', process.env.PORT);const http = http.createServer(app);\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"15. 什麼是 yarn 和 npm?爲什麼要用 yarn 代替 npm 呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"npm 是與 Node.js 自帶的默認包管理器,它有一個大型的公共庫和私有庫,存儲在 npm registry 的數據庫中(譯者注,官方默認中心庫 http:\/\/registry.npmjs.org\/,國內淘寶鏡像 http:\/\/registry.npm.taobao.org\/),用戶可以通過 npm 命令行訪問該數據庫。在 npm 的幫助下,用戶可以輕鬆管理項目中的依賴項。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"yarn 也是一個包管理器,爲了解決 npm 的一些缺點。yarn 依賴 npm 註冊中心爲用戶提供對包訪問。yarn 底層結構基於 npm,如果從 npm 遷移到 yarn,項目結構和工作流不需要大改。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"就像之前提到的,在某些情況下,yarn 提供了比 npm 更好的功能。與 npm 不同的是,它會緩存下載的每個包,不必重新下載。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過校驗和驗證包的完整性來提供更好的安全性,保證在某個系統上運行的包在任何其他系統中的工作方式完全相同,這就是爲什麼選擇 yarn 而不是 npm 來進行包管理。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"結論"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在本文中,討論了 15 個最常見的 Node.js 面試問題,幫助你爲下一次面試做準備。知道你可能被問到的問題和答案,面試就不再緊張了。"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章