摘要:模塊系統為了讓的文件可以相互調用,提供了一個簡單的模塊系統。但是,沒有模塊系統。包管理簡稱,是隨同一起安裝的包管理工具。輸入命令,根據提示配置包的相關信息,生成相應的。以上所描述的模塊載入機制均定義在模塊之中。
Node.js簡介
首先從名字說起,網上查閱資料的時候會發現關于node的寫法五花八門,到底哪一種寫法最標準呢?遵循官方網站的說法,一直將項目稱之為“Node”或者“Node.js”。
簡單來說,Node就是運行在服務器端的JavaScript。
JavaScript是一門腳本語言(可以用來編程的并且直接執行源代碼的語言,就是腳本語言),腳本語言都需要一個解析器才能運行。對于寫在html中的js,通常是由瀏覽器去解析執行。對于獨立執行的js代碼,則需要Node這個解析器解析執行。
每一種解析器就是一個運行環境,不但允許js定義各種數據結構,進行各種計算,還允許js使用運行環境提供的內置對象和方法做一些事情。例如運行在瀏覽器中的js的用途是操作DOM,瀏覽器提供了document之類的內置對象。而運行在node中的js的用途是操作磁盤文件或搭建HTTP服務器,node就相應提供了fs、http等內置對象。
Node不是js應用,而是js的運行環境。
看到Node.js這個名字,可能會誤以為這是一個JavaScript應用,事實上,node采用c++語言對Google V8引擎進行了封裝,是一個JavaScript運行環境。V8引擎執行JavaScript的速度非常快,性能也非常好。node是一個讓開發者可以快速創建網絡應用的服務端JavaScript平臺,同時運用JavaScript進行前端與后端編程,從而開發者可以更專注于系統的設計以及保持其一致性。
// 快速構建服務器 const http = require("http") http.createServer((req,res)=>{ res.writeHead(200, {"Content-Type": "text/plain"}) res.end("hello World!") }).listen(8088) $ node helloWorld.js
Node采用事件驅動、異步編程
node的設計思想以事件驅動為核心,它提供的絕大多數API都是基于事件的、異步的風格。開發者需要根據自己的業務邏輯注冊相應的回調函數,這些回調函數都是異步執行的。這意味著雖然在代碼結構中,這些函數看似是依次注冊的,但是它們并不依賴自身出現的順序,而是等待相應的事件觸發。
在服務器開發中,并發的請求處理是個大問題,阻塞式的函數會導致資源浪費和時間延遲。通過事件注冊、異步函數,開發者可以充分利用系統資源,執行代碼無須阻塞等待,有限的資源可以用于其他的任務。
Node以單進程、單線程模式運行
這點和JavaScript的運行方式一致,事件驅動機制是node通過內部單線程高效率地維護事件循環隊列來實現的,沒有多線程的資源占用和上下文切換,這意味著面對大規模的http請求,node憑借事件驅動搞定一切。由此我們是否可以推測這樣的設計會導致負載的壓力集中在CPU(事件循環處理?)而不是內存。淘寶共享數據平臺團隊對node的性能測試:
物理機配置:RHEL 5.2、CPU 2.2GHz、內存4G
Node.js應用場景:MemCache代理,每次取100字節數據
連接池大小:50
并發用戶數:100
測試結果(socket模式):內存(30M)、QPS(16700)、CPU(95%)
眼見為實,雖然看不太懂這些測試數據,但是最終測試結果是:它的性能讓人信服。
Node.js模塊系統為了讓Node.js的文件可以相互調用,Node.js提供了一個簡單的模塊系統。模塊系統是Node組織管理代碼的利器也是調用第三方代碼的途徑。
模塊是Node應用程序的基本組成部分,文件和模塊是一一對應的。換言之,一個 Node.js 文件就是一個模塊,這個文件可能是JavaScript 代碼、JSON 或者編譯過的C/C++ 擴展。
理想情況下,開發者只需要實現核心的業務邏輯,其他都可以加載別人已經寫好的模塊。但是,
JavaScript沒有模塊系統。沒有原生的支持密閉作用域或依賴管理。
JavaScript沒有標準庫。除了一些核心庫外,沒有文件系統的API,沒有IO流API等。
JavaScript沒有標準接口。沒有如Web Server或者數據庫的統一接口。
JavaScript沒有包管理系統。不能自動加載和安裝依賴。
要想實現模塊化編程首先需要解決的問題是,命名沖突以及文件依賴問題。
CommonJS規范于是便有了CommonJS規范的出現,其目標是為了構建JavaScript在包括web服務器,桌面,命令行工具,以及瀏覽器方面的生態系統。CommonJS制定了解決這些問題的一些規范,而node就是這些規范的一種實現。node自身實現了require方法作為其引入模塊的方法,同時npm也基于CommonJS定義的包規范,實現了依賴管理和模塊自動安裝等功能。
Node中模塊分類原生模塊即為Node API提供的核心模塊(如:os、http、fs、buffer、path等模塊),原生模塊在node源代碼編譯的時候編譯進了二進制執行文件,加載的速度最快。
const http = require("http");
為動態加載模塊,動態加載的模塊主要由原生模塊module來實現和完成。原生模塊在啟動時已經被加載,而文件模塊需要通過調用module的require方法來實現加載。
首先定義一個文件模塊,以計算圓形的面積和周長兩個方法為例:
const PI = Math.PI; exports.area = (r) => { return PI * r * r; }; exports.circumference = (r) => { return 2 * PI * r; };
將這個文件存為circle.js,并新建一個app.js文件,并寫入以下代碼:
// 調用文件模塊必須指定路徑,否則會報錯 const circle = require("./circle.js"); console.log( "The area of a circle of radius 4 is " + circle.area(4));
在require了這個文件之后,定義在exports對象上的方法便可以隨意調用。
包管理Node Packaged Modules 簡稱NPM,是隨同node一起安裝的包管理工具。Node本身提供了一些基本API模塊,但是這些基本模塊難以滿足開發者需求。Node需要通過使用NPM來管理開發者自我研發的一些模塊,并使其能夠公用與其他的開發者。
NPM建立了一個node生態圈,node開發者和用戶可以在里邊互通有無。當你需要下載第三方包時,首先要知道有哪些包可用 npmjs.com 提供了可以根據包名來搜索的平臺。知道包名后就可以使用命令去安裝了。
npm -v // 測試是否安裝成功。
npm install moduleNames
npm install moduleNames -g // 全局安裝 npm install moduleNames@2.0.0 // 安裝特定版本依賴 npm install moduleNames --save // --save 可簡寫為 -S // 會在package.json的dependencies屬性下添加moduleNames依賴 即生產依賴插件 npm install moduleNames --save-dev // --save-dev 可簡寫為 -D // 會在package.json的devDependencies屬性下添加moduleNames依賴 即開發依賴插件
卸載模塊
npm uninstall moduleNames
更新模塊
npm update moduleNames
搜索模塊
npm search moduleNames
切換模板倉庫源:
npm config set registry https://registry.npm.taobao.org/ npm config get registry // 執行驗證是否切換成功
第一次使用NPM發布自己的包需要在 npmjs.com 注冊一個賬號。也可以使用命令 npm adduser,提示輸入賬號,密碼和郵箱,然后將提示創建成功("Logged in as Username on https://registry.npmjs.org/.")。
輸入npm init命令,根據提示配置包的相關信息,生成相應的package.json。npm命令運行時會讀取當前目錄的 package.json 文件和解釋這個文件
通過npm publish發包,包的名稱和版本就是你項目里package.json的name和vision。此處注意:
name不能和已有包的名字重名。
name不能有大寫字母/空格/下劃線。
不想發布到npm上的代碼文件將它寫入.gitignore或.npmignore中再上傳。
更新包和發布包的命令一樣,但是每次更新別忘記修改包的版本。
模塊初始化一個模塊中的JavaScript代碼僅在模塊第一次被使用時執行一次,并在執行過程中初始化模塊的導出對象。之后,緩存起來的導出對象被重復利用。其中原生模塊都被定義在lib這個目錄下面,文件模塊則不定性。
模塊加載的優先級模塊加載的優先級:已經緩存模塊 > 原生模塊 > 文件模塊 > 從文件加載
盡管require方法很簡單,但是內部的加載卻是十分復雜的
,其加載優先級也各自不同。如下圖示:
原生模塊的優先級僅次于文件模塊緩存的優先級。require方法在解析文件名之后,優先檢查模塊是否在原生模塊列表中。
原生模塊也有一個緩存區,同樣也是優先從緩存區加載。如果緩存區沒有被加載過,則調用原生模塊的加載方式進行加載和執行。
實際上,在文件模塊中又分為三類模塊,以后綴為區分,node會根據后綴名來決定加載方法。
.js 通過fs模塊同步讀取js文件并編譯執行。
.node 通過c/c++進行編寫的Addon。通過dlopen方法進行加載。
.json 讀取文件,調用JSON.parse解析加載。
當文件模塊緩存中不存在,而且也不是原生模塊的時候,node會解析require方法傳入的參數,并從文件系統中加載實際的文件。
加載文件模塊的工作主要有原生模塊module來實現和完成,該原生模塊在啟動時已經被加載,進程直接調用到runMain靜態方法。
Module.runMain = function () { Module._load(process.argv[1], null, true); };
_load靜態方法在分析文件名之后執行
var module = new Module(id, parent);
并根據文件路徑緩存當前模塊對象,該模塊實例對象則根據文件名加載。
module.load(filename);
以.js后綴的文件為例,node在編譯js文件的過程中實際完成的步驟是對js文件內容進行頭尾包裝。例如剛才的app.js,在包裝之后變成這個樣子:
(function (exports, require, module, __filename, __dirname) { var circle = require("./circle.js"); console.log("The area of a circle of radius 4 is " + circle.area(4)); });
這段代碼擁有明確的上下文,不污染全局,返回為一個具體的function對象。最后傳入module對象的exports,require方法,module,文件名,目錄名作為實參并執行。
這就是為什么require并有定義在app.js文件中,但是這個方法卻存在的原因。在這個主文件中,可以通過require方法去引入其余的模塊。而其實這個require方法實際調用的就是load方法。
load方法在載入、編譯、緩存了module后,返回module的exports對象。這就是circle.js文件中只有定義在exports對象上的方法才能被外部調用的原因。
以上所描述的模塊載入機制均定義在module模塊之中。
require方法接受以下幾種參數的傳遞:
http、fs、path等,原生模塊。
./mod或../mod,相對路徑的文件模塊。
/pathtomodule/mod, 絕對路徑的文件模塊。
mod,非原生模塊的文件模塊。
在進入路徑查找之前有必要描述以下module path這個node中的概念。對于每一個被加載的文件模塊,創建這個模塊對象的時候,這個模塊便會有一個paths屬性,它的值根據當前文件的路徑計算得到。
例:
我們創建modulepath.js這樣一個文件,其內容為:
console.log(module.paths);
執行node modulepath.js,將得到以下的輸出結果:
[ "/Users/zhaoyunlong/Node/demo/node_modules", "/Users/zhaoyunlong/Node/node_modules", "/Users/zhaoyunlong/node_modules", "/Users/node_modules", "/node_modules" ]
Windows下:
[ "E:Extraminiprogramgm-xcc-demogm-demo ode_modules", "E:Extraminiprogramgm-xcc-demo ode_modules", "E:Extraminiprogram ode_modules", "E:Extra ode_modules", "E: ode_modules" ]
可以看出module path的生成規則為:從當前文件目錄開始查找node_modules目錄;然后依次進入父目錄,查找父目錄的node_modules目錄;依次迭代,直到根目錄下的node_modules目錄。
除此之外還有一個全局module path,是當前node執行文件的相對目錄(../../lib/node)。如果在環境變量中設置了HOME目錄和NODE_PATH目錄的話,整個路徑還包含NODE_PATH和HOME目錄下的.node_libraries與.node_modules。其最終值大致如下:
[ NODE_PATH,HOME/.node_modules,HOME/.node_libraries,execPath/../../lib/node ]
簡單說就是,如果require絕對路徑的文件,查找時不會去遍歷每一個node_modules目錄,其速度最快。其余流程如下:
從module path 數組中取出第一個目錄作為查找基準。
直接從目錄中查找該文件,如果存在,則結束查找。如果不存在,則進行下一條查找。
嘗試添加.js、.json、.node后綴后查找,如果存在文件,則結束。如果不存在,則進行下一條。
嘗試將require的參數作為一個包來進行查找,讀取目錄下的package.json文件,取得main參數指定的文件。
嘗試查找該文件,如果存在,則結束查找。如果不存在則進行第3條查找。
如果繼續失敗,則取出module path數組中的下一個目錄作為基準查找,循環第1至5個步驟。
如果繼續失敗,循環第1至6個步驟,直到module path中的最后一個值。
如果仍然失敗,則拋出異常。
整個查找過程十分類似JavaScript原型鏈的查找和作用域的查找。不同的是node對路徑查找實現了緩存機制,否則每次判斷路徑都是同步阻塞式進行,會導致嚴重的性能消耗。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/96932.html
摘要:為指定事件注冊一個監聽器,接受一個字符串和一個回調函數。發射事件,傳遞若干可選參數到事件監聽器的參數表。為指定事件注冊一個單次監聽器,即監聽器最多只會觸發一次,觸發后立刻解除該監聽器。 1.Node.js 簡介 Node.js 其實就是借助谷歌的 V8 引擎,將桌面端的 js 帶到了服務器端,它的出現我將其歸結為兩點: V8 引擎的出色; js 異步 io 與事件驅動給服務器帶來極高...
摘要:前言這里筑夢師是一名正在努力學習的開發工程師目前致力于全棧方向的學習希望可以和大家一起交流技術共同進步用簡書記錄下自己的學習歷程個人學習方法分享本文目錄更新說明目錄學習方法學習態度全棧開發學習路線很長知識拓展很長在這里收取很多人的建議以后決 前言 這里筑夢師,是一名正在努力學習的iOS開發工程師,目前致力于全棧方向的學習,希望可以和大家一起交流技術,共同進步,用簡書記錄下自己的學習歷程...
閱讀 3156·2021-11-08 13:18
閱讀 2292·2019-08-30 15:55
閱讀 3614·2019-08-30 15:44
閱讀 3077·2019-08-30 13:07
閱讀 2788·2019-08-29 17:20
閱讀 1954·2019-08-29 13:03
閱讀 3419·2019-08-26 10:32
閱讀 3232·2019-08-26 10:15