首先從名字提及,網上查閱資料的時候會發現關於node的寫法五花八門,到底哪種寫法最標準呢?遵循官方網站的說法,一直將項目稱之爲「Node」或者「Node.js」。html
簡單來講,Node就是運行在服務器端的JavaScript。
JavaScript是一門腳本語言(能夠用來編程的而且直接執行源代碼的語言,就是腳本語言),腳本語言都須要一個解析器才能運行。對於寫在html中的js,一般是由瀏覽器去解析執行。對於獨立執行的js代碼,則須要Node這個解析器解析執行。前端
每一種解析器就是一個運行環境,不但容許js定義各類數據結構,進行各類計算,還容許js使用運行環境提供的內置對象和方法作一些事情。例如運行在瀏覽器中的js的用途是操做DOM,瀏覽器提供了document之類的內置對象。而運行在node中的js的用途是操做磁盤文件或搭建HTTP服務器,node就相應提供了fs、http等內置對象。node
Node不是js應用,而是js的運行環境。
看到Node.js這個名字,可能會誤覺得這是一個JavaScript應用,事實上,node採用c++語言對Google V8引擎進行了封裝,是一個JavaScript運行環境。V8引擎執行JavaScript的速度很是快,性能也很是好。node是一個讓開發者能夠快速建立網絡應用的服務端JavaScript平臺,同時運用JavaScript進行前端與後端編程,從而開發者能夠更專一於系統的設計以及保持其一致性。c++
// 快速構建服務器 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都是基於事件的、異步的風格。開發者須要根據本身的業務邏輯註冊相應的回調函數,這些回調函數都是異步執行的。這意味着雖然在代碼結構中,這些函數看似是依次註冊的,可是它們並不依賴自身出現的順序,而是等待相應的事件觸發。git
在服務器開發中,併發的請求處理是個大問題,阻塞式的函數會致使資源浪費和時間延遲。經過事件註冊、異步函數,開發者能夠充分利用系統資源,執行代碼無須阻塞等待,有限的資源能夠用於其餘的任務。web
Node以單進程、單線程模式運行
這點和JavaScript的運行方式一致,事件驅動機制是node經過內部單線程高效率地維護事件循環隊列來實現的,沒有多線程的資源佔用和上下文切換,這意味着面對大規模的http請求,node憑藉事件驅動搞定一切。由此咱們是否能夠推測這樣的設計會致使負載的壓力集中在CPU(事件循環處理?)而不是內存。淘寶共享數據平臺團隊對node的性能測試:數據庫
眼見爲實,雖然看不太懂這些測試數據,可是最終測試結果是:它的性能讓人信服。npm
爲了讓Node.js的文件能夠相互調用,Node.js提供了一個簡單的模塊系統。模塊系統是Node組織管理代碼的利器也是調用第三方代碼的途徑。
模塊是Node應用程序的基本組成部分,文件和模塊是一一對應的。換言之,一個 Node.js 文件就是一個模塊,這個文件多是JavaScript 代碼、JSON 或者編譯過的C/C++ 擴展。編程
理想狀況下,開發者只須要實現核心的業務邏輯,其餘均可以加載別人已經寫好的模塊。可是,json
要想實現模塊化編程首先須要解決的問題是,命名衝突以及文件依賴問題。
因而便有了CommonJS規範的出現,其目標是爲了構建JavaScript在包括web服務器,桌面,命令行工具,以及瀏覽器方面的生態系統。CommonJS制定瞭解決這些問題的一些規範,而node就是這些規範的一種實現。node自身實現了require方法做爲其引入模塊的方法,同時npm也基於CommonJS定義的包規範,實現了依賴管理和模塊自動安裝等功能。
原生模塊即爲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。此處注意:
一個模塊中的JavaScript代碼僅在模塊第一次被使用時執行一次,並在執行過程當中初始化模塊的導出對象。以後,緩存起來的導出對象被重複利用。其中原生模塊都被定義在lib這個目錄下面,文件模塊則不定性。
模塊加載的優先級:已經緩存模塊 > 原生模塊 > 文件模塊 > 從文件加載
儘管require方法很簡單,可是內部的加載倒是十分複雜的
,其加載優先級也各自不一樣。以下圖示:
原生模塊的優先級僅次於文件模塊緩存的優先級。require方法在解析文件名以後,優先檢查模塊是否在原生模塊列表中。
原生模塊也有一個緩存區,一樣也是優先從緩存區加載。若是緩存區沒有被加載過,則調用原生模塊的加載方式進行加載和執行。
實際上,在文件模塊中又分爲三類模塊,之後綴爲區分,node會根據後綴名來決定加載方法。
當文件模塊緩存中不存在,並且也不是原生模塊的時候,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方法接受如下幾種參數的傳遞:
在進入路徑查找以前有必要描述如下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:\\Extra\\miniprogram\\gm-xcc-demo\\gm-demo\\node_modules', 'E:\\Extra\\miniprogram\\gm-xcc-demo\\node_modules', 'E:\\Extra\\miniprogram\\node_modules', 'E:\\Extra\\node_modules', 'E:\\node_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目錄,其速度最快。其他流程以下:
整個查找過程十分相似JavaScript原型鏈的查找和做用域的查找。不一樣的是node對路徑查找實現了緩存機制,不然每次判斷路徑都是同步阻塞式進行,會致使嚴重的性能消耗。