Node.js入門:模塊機制

CommonJS規範 前端

早在Netscape誕生不久後,JavaScript就一直在探索本地編程的路,Rhino是其表明產物。無奈那時服務端JavaScript走的路均是參考衆多服務器端語言來實現的,在這樣的背景之下,一沒有特點,二沒有實用價值。可是隨着JavaScript在前端的應用愈來愈普遍,以及服務端JavaScript的推進,JavaScript現有的規範十分薄弱,不利於JavaScript大規模的應用。那些以JavaScript爲宿主語言的環境中,只有自己的基礎原生對象和類型,更多的對象和API都取決於宿主的提供,因此,咱們能夠看到JavaScript缺乏這些功能:node

  • JavaScript沒有模塊系統。沒有原生的支持密閉做用域或依賴管理。 
  • JavaScript沒有標準庫。除了一些核心庫外,沒有文件系統的API,沒有IO流API等。 
  • JavaScript沒有標準接口。沒有如Web Server或者數據庫的統一接口。 
  • JavaScript沒有包管理系統。不能自動加載和安裝依賴。 

因而便有了CommonJS(http://www.commonjs.org)規範的出現,其目標是爲了構建JavaScript在包括Web服務器,桌面,命令行工具,及瀏覽器方面的生態系統。CommonJS制定瞭解決這些問題的一些規範,而Node.js就是這些規範的一種實現。Node.js自身實現了require方法做爲其引入模塊的方法,同時NPM也基於CommonJS定義的包規範,實現了依賴管理和模塊自動安裝等功能。這裏咱們將深刻一下Node.js的require機制和NPM基於包規範的應用。數據庫

簡單模塊定義和使用編程

在Node.js中,定義一個模塊十分方便。咱們以計算圓形的面積和周長兩個方法爲例,來表現Node.js中模塊的定義方式。json

1 var PI = Math.PI; 
2 exports.area = function (r) { 
3  return PI * r * r; 
4 }; 
5 exports.circumference = function (r) {
 6  return 2 * PI * r; 
7 };</pre>
}//歡迎加入全棧開發交流圈一塊兒學習交流:582735936
]//面向1-3年前端人員
}   //幫助突破技術瓶頸,提高思惟能力

將這個文件存爲circle.js,並新建一個app.js文件,並寫入如下代碼:bootstrap

1 var circle = require('./circle.js'); 
2 console.log( 'The area of a circle of radius 
3 is ' + circle.area(
4));</pre>

能夠看到模塊調用也十分方便,只須要require須要調用的文件便可。瀏覽器

在require了這個文件以後,定義在exports對象上的方法即可以隨意調用。Node.js將模塊的定義和調用都封裝得極其簡單方便,從API對用戶友好這一個角度來講,Node.js的模塊機制是很是優秀的。緩存

模塊載入策略服務器

Node.js的模塊分爲兩類,一類爲原生(核心)模塊,一類爲文件模塊。原生模塊在Node.js源代碼編譯的時候編譯進了二進制執行文件,加載的速度最快。另外一類文件模塊是動態加載的,加載速度比原生模塊慢。可是Node.js對原生模塊和文件模塊都進行了緩存,因而在第二次require時,是不會有重複開銷的。其中原生模塊都被定義在lib這個目錄下面,文件模塊則不定性。app

node app.js

因爲經過命令行加載啓動的文件幾乎都爲文件模塊。咱們從Node.js如何加載文件模塊開始談起。加載文件模塊的工做,主要由原生模塊module來實現和完成,該原生模塊在啓動時已經被加載,進程直接調用到runMain靜態方法。

1 // bootstrap main module. 
2 Module.runMain = function () {
 3     // Load the main module--the command line argument. 
4     Module._load(process.argv[1], null, true); 5 };</pre>

_load靜態方法在分析文件名以後執行

var module = new Module(id, parent);

並根據文件路徑緩存當前模塊對象,該模塊實例對象則根據文件名加載。

module.load(filename);

實際上在文件模塊中,又分爲3類模塊。這三類文件模塊之後綴來區分,Node.js會根據後綴名來決定加載方法。

  • .js。經過fs模塊同步讀取js文件並編譯執行。 
  • .node。經過C/C++進行編寫的Addon。經過dlopen方法進行加載。 
  • .json。讀取文件,調用JSON.parse解析加載。

這裏咱們將詳細描述js後綴的編譯過程。Node.js在編譯js文件的過程當中實際完成的步驟有對js文件內容進行頭尾包裝。

以app.js爲例,包裝以後的app.js將會變成如下形式:

1 (function (exports, require, module, __filename, __dirname) { 
2     var circle = require('./circle.js'); 
3     console.log('The area of a circle of radius
 4 is ' + circle.area(4)); 4 });</pre>

這段代碼會經過vm原生模塊的runInThisContext方法執行(相似eval,只是具備明確上下文,不污染全局),返回爲一個具體的function對象。最後傳入module對象的exports,require方法,module,文件名,目錄名做爲實參並執行。

這就是爲何require並無定義在app.js 文件中,可是這個方法卻存在的緣由。從Node.js的API文檔中能夠看到還有__filename、__dirname、module、exports幾個沒有定義可是卻存在的變量。其中__filename和__dirname在查找文件路徑的過程當中分析獲得後傳入的。module變量是這個模塊對象自身,exports是在module的構造函數中初始化的一個空對象({},而不是null)。

在這個主文件中,能夠經過require方法去引入其他的模塊。而其實這個require方法實際調用的就是load方法。

load方法在載入、編譯、緩存了module後,返回module的exports對象。這就是circle.js文件中只有定義在exports對象上的方法才能被外部調用的緣由。

以上所描述的模塊載入機制均定義在lib/module.js中。

相關文章
相關標籤/搜索