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