原文連接:https://www.jianshu.com/p/33d53cce8237javascript
原文系列2連接:https://www.jianshu.com/p/ad427d8879cb html
前端徹底手冊: https://leohxj.gitbooks.io/front-end-database/content/javascript-oop/encapsulation.html前端
本文參考
Javascript模塊化編程(一):模塊的寫法
Javascript模塊化編程(二):AMD規範
Javascript模塊化編程(三):require.js的用法java
隨着網站逐漸變成"互聯網應用程序",嵌入網頁的Javascript代碼愈來愈龐大,愈來愈複雜。網頁愈來愈像桌面程序,須要一個團隊分工協做、進度管理、單元測試等等......開發者不得不使用軟件工程的方法,管理網頁的業務邏輯。Javascript模塊化編程,已經成爲一個迫切的需求。理想狀況下,開發者只須要實現核心的業務邏輯,其餘均可以加載別人已經寫好的模塊。可是,Javascript不是一種模塊化編程語言,它不支持"類"(class),更遑論"模塊"(module)了。(正在制定中的ECMAScript標準第六版,將正式支持"類"和"模塊",但還須要很長時間才能投入實用。)Javascript社區作了不少努力,在現有的運行環境中,實現"模塊"的效果。本文總結了當前"Javascript模塊化編程"的最佳實踐,說明如何投入實用。雖然這不是初級教程,可是隻要稍稍瞭解Javascript的基本語法,就能看懂。node
模塊就是實現特定功能的一組方法。只要把不一樣的函數(以及記錄狀態的變量)簡單地放在一塊兒,就算是一個模塊。git
function m1(){ //... } function m2(){ //... }
上面的函數m1()和m2(),組成一個模塊。使用的時候,直接調用就好了。這種作法的缺點很明顯:"污染"了全局變量,沒法保證不與其餘模塊發生變量名衝突,並且模塊成員之間看不出直接關係。程序員
爲了解決上面的缺點,能夠把模塊寫成一個對象,全部的模塊成員都放到這個對象裏面。github
var module1 = new Object({ _count : 0, m1 : function (){ //... }, m2 : function (){ //... } });
上面的函數m1()和m2(),都封裝在module1對象裏。使用的時候,就是調用這個對象的屬性。module1.m1();
可是,這樣的寫法會暴露全部模塊成員,內部狀態能夠被外部改寫。好比,外部代碼能夠直接改變內部計數器的值。module1._count = 5;
web
其實這種寫法在ES5中又叫命名空間,參考 JS命名空間的使用express
var MYNAMESPACE = MYNAMESPACE || {}; MYNAMESPACE.person = function(name) { this.name = name; }; MYNAMESPACE.person.prototype.getName = function() { return this.name; }; // 使用方法 var p = new MYNAMESPACE.person("doc"); p.getName();
var myMasterNS = myMasterNS || {}; myMasterNS.mySubNS = myMasterNS.mySubNS || {}; myMasterNS.mySubNS.someFunction = function(){ //插入邏輯 };
這裏咱們聲明瞭一個名爲 myMasterNS 的簡單全局對象,又分配了一個子命名空間對象做爲原來命名空間的一個屬性,這個例子中是 mySubNS。如今咱們可以在頂級命名空間下實現任何所需的功能,而且任何後面的子命名空間都不會污染全局做用域。請注意,這不是使用 JavaScript 實現命名空間的惟一模式。我在這個例子中選擇它,是由於這種模式的簡單和可讀性。
使用"當即執行函數"(Immediately-Invoked Function Expression,IIFE),能夠達到不暴露私有成員的目的。
var module1 = (function(){ var _count = 0; var m1 = function(){ //... }; var m2 = function(){ //... }; return { m1 : m1, m2 : m2 }; })();
使用上面的寫法,外部代碼沒法讀取內部的_count變量。console.info(module1._count); //undefined
module1就是Javascript模塊的基本寫法。下面,再對這種寫法進行加工。
若是一個模塊很大,必須分紅幾個部分,或者一個模塊須要繼承另外一個模塊,這時就有必要採用"放大模式"(augmentation)。
var module1 = (function (mod){ mod.m3 = function () { //... }; return mod; })(module1);
上面的代碼爲module1模塊添加了一個新方法m3(),而後返回新的module1模塊。
在瀏覽器環境中,模塊的各個部分一般都是從網上獲取的,有時沒法知道哪一個部分會先加載。若是採用上一節的寫法,第一個執行的部分有可能加載一個不存在空對象,這時就要採用"寬放大模式"。
var module1 = ( function (mod){ //... return mod; })(window.module1 || {});
與"放大模式"相比,"寬放大模式"就是"當即執行函數"的參數能夠是空對象。
獨立性是模塊的重要特色,模塊內部最好不與程序的其餘部分直接交互。爲了在模塊內部調用全局變量,必須顯式地將其餘變量輸入模塊。
var module1 = (function ($, YAHOO) { //... })(jQuery, YAHOO);
上面的module1模塊須要使用jQuery庫和YUI庫,就把這兩個庫(實際上是兩個模塊)看成參數輸入module1。這樣作除了保證模塊的獨立性,還使得模塊之間的依賴關係變得明顯。這方面更多的討論,參見Ben Cherry的著名文章《JavaScript Module Pattern: In-Depth》。
先想想,爲何模塊很重要?由於有了模塊,咱們就能夠更方便地使用別人的代碼,想要什麼功能,就加載什麼模塊。可是,這樣作有一個前提,那就是你們必須以一樣的方式編寫模塊,不然你有你的寫法,我有個人寫法,豈不是亂了套!考慮到Javascript模塊如今尚未官方規範,這一點就更重要了。目前,通行的Javascript模塊規範共有兩種:CommonJS和AMD。我主要介紹AMD,可是要先從CommonJS講起。
2009年,美國程序員Ryan Dahl創造了node.js項目,將javascript語言用於服務器端編程。這標誌"Javascript模塊化編程"正式誕生。由於老實說,在瀏覽器環境下,沒有模塊也不是特別大的問題,畢竟網頁程序的複雜性有限;可是在服務器端,必定要有模塊,與操做系統和其餘應用程序互動,不然根本無法編程。
node.js的模塊系統,就是參照CommonJS規範實現的。在CommonJS中,有一個全局性方法require(),用於加載模塊。假定有一個數學模塊math.js,就能夠像下面這樣加載。
var math = require('math');
而後,就能夠調用模塊提供的方法:
var math = require('math'); math.add(2,3); // 5
關於CommonJs後文詳述
有了服務器端模塊之後,很天然地,你們就想要客戶端模塊。並且最好二者可以兼容,一個模塊不用修改,在服務器和瀏覽器均可以運行。可是,因爲一個重大的侷限,使得CommonJS規範不適用於瀏覽器環境。仍是上一節的代碼,若是在瀏覽器中運行,會有一個很大的問題,你能看出來嗎?
var math = require('math'); math.add(2, 3);
第二行math.add(2, 3),在第一行require('math')以後運行,所以必須等math.js加載完成。也就是說,若是加載時間很長,整個應用就會停在那裏等。這對服務器端不是一個問題,由於全部的模塊都存放在本地硬盤,能夠同步加載完成,等待時間就是硬盤的讀取時間。可是,對於瀏覽器,這倒是一個大問題,由於模塊都放在服務器端,等待時間取決於網速的快慢,可能要等很長時間,瀏覽器處於"假死"狀態。所以,瀏覽器端的模塊,不能採用"同步加載"(synchronous),只能採用"異步加載"(asynchronous)。這就是AMD規範誕生的背景。
AMD是"Asynchronous Module Definition"的縮寫,意思就是"異步模塊定義"。它採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。全部依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成以後,這個回調函數纔會運行。AMD也採用require()語句加載模塊,可是不一樣於CommonJS,它要求兩個參數:
require([module], callback);
第一個參數[module],是一個數組,裏面的成員就是要加載的模塊;第二個參數callback,則是加載成功以後的回調函數。若是將前面的代碼改寫成AMD形式,就是下面這樣:
require(['math'], function (math) { math.add(2, 3); });
math.add()與math模塊加載不是同步的,瀏覽器不會發生假死。因此很顯然,AMD比較適合瀏覽器環境。目前,主要有兩個Javascript庫實現了AMD規範:require.js和curl.js。本系列的第三部分,將經過介紹require.js,進一步講解AMD的用法,以及如何將模塊化編程投入實戰。
最先的時候,全部Javascript代碼都寫在一個文件裏面,只要加載這一個文件就夠了。後來,代碼愈來愈多,一個文件不夠了,必須分紅多個文件,依次加載。下面的網頁代碼,相信不少人都見過。
<script src="1.js"></script> <script src="2.js"></script> <script src="3.js"></script> <script src="4.js"></script> <script src="5.js"></script> <script src="6.js"></script>
這段代碼依次加載多個js文件。這樣的寫法有很大的缺點。首先,加載的時候,瀏覽器會中止網頁渲染,加載文件越多,網頁失去響應的時間就會越長;其次,因爲js文件之間存在依賴關係,所以必須嚴格保證加載順序(好比上例的1.js要在2.js的前面),依賴性最大的模塊必定要放到最後加載,當依賴關係很複雜的時候,代碼的編寫和維護都會變得困難。require.js的誕生,就是爲了解決這兩個問題:
(1)實現js文件的異步加載,避免網頁失去響應;
(2)管理模塊之間的依賴性,便於代碼的編寫和維護。
使用require.js的第一步,是先去官方網站下載最新版本。下載後,假定把它放在js子目錄下面,就能夠加載了。
<script src="js/require.js"></script>
有人可能會想到,加載這個文件,也可能形成網頁失去響應。解決辦法有兩個,一個是把它放在網頁底部加載,另外一個是寫成下面這樣:
<script src="js/require.js" defer async="true" ></script>
async屬性代表這個文件須要異步加載,避免網頁失去響應。IE不支持這個屬性,只支持defer,因此把defer也寫上。加載require.js之後,下一步就要加載咱們本身的代碼了。假定咱們本身的代碼文件是main.js,也放在js目錄下面。那麼,只須要寫成下面這樣就好了:
<script src="js/require.js" data-main="js/main"></script>
data-main屬性的做用是,指定網頁程序的主模塊。在上例中,就是js目錄下面的main.js,這個文件會第一個被require.js加載。因爲require.js默認的文件後綴名是js,因此能夠把main.js簡寫成main。
上一節的main.js,我把它稱爲"主模塊",意思是整個網頁的入口代碼。它有點像C語言的main()函數,全部代碼都從這兒開始運行。下面就來看,怎麼寫main.js。若是咱們的代碼不依賴任何其餘模塊,那麼能夠直接寫入javascript代碼。
// main.js alert("加載成功!");
但這樣的話,就不必使用require.js了。真正常見的狀況是,主模塊依賴於其餘模塊,這時就要使用AMD規範定義的的require()函數。
// main.js require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){ // some code here });
require()函數接受兩個參數。第一個參數是一個數組,表示所依賴的模塊,上例就是['moduleA', 'moduleB', 'moduleC'],即主模塊依賴這三個模塊;第二個參數是一個回調函數,當前面指定的模塊都加載成功後,它將被調用。加載的模塊會以參數形式傳入該函數,從而在回調函數內部就可使用這些模塊。require()異步加載moduleA,moduleB和moduleC,瀏覽器不會失去響應;它指定的回調函數,只有前面的模塊都加載成功後,纔會運行,解決了依賴性的問題。
更多細節參考
http://www.requirejs.cn
【JavaScript】RequireJS模塊化之HelloWorld
參考
淺析JS模塊規範:AMD,CMD,CommonJS
AMD 和 CMD 的區別有哪些?
AMD雖然實現了異步加載,可是開始就把全部依賴寫出來是不符合書寫的邏輯順序的,能不能像commonJS那樣用的時候再require,並且還支持異步加載後再執行呢?CMD (Common Module Definition), 是seajs推崇的規範,CMD則是依賴就近,用的時候再require。它寫起來是這樣的:
define(function(require, exports, module) { var clock = require('clock'); clock.start(); });
AMD和CMD最大的區別是對依賴模塊的執行時機處理不一樣,而不是加載的時機或者方式不一樣,兩者皆爲異步加載模塊。
AMD依賴前置,js能夠方便知道依賴模塊是誰,當即加載;而CMD就近依賴,須要使用把模塊變爲字符串解析一遍才知道依賴了那些模塊,這也是不少人詬病CMD的一點,犧牲性能來帶來開發的便利性,實際上解析模塊用的時間短到能夠忽略。
UMD是AMD和CommonJS的糅合。AMD模塊以瀏覽器第一的原則發展,異步加載模塊。CommonJS模塊以服務器第一原則發展,選擇同步加載,它的模塊無需包裝(unwrapped modules)。這迫令人們又想出另外一個更通用的模式UMD (Universal Module Definition)。但願解決跨平臺的解決方案。
UMD先判斷是否支持Node.js的模塊(exports)是否存在,存在則使用Node.js模塊模式。在判斷是否支持AMD(define是否存在),存在則使用AMD方式加載模塊。
(function (window, factory) { if (typeof exports === 'object') { module.exports = factory(); } else if (typeof define === 'function' && define.amd) { define(factory); } else { window.eventUtil = factory(); } })(this, function () { //module ... });
CommonJS團隊定義了module格式來解決JavaScript做用域問題,這樣確保了每個module都在本身的命名空間下執行。根據CommonJS的規範,每一個文件就是一個模塊,有本身的做用域。在一個文件裏面定義的變量、函數、類,都是私有的,對其餘文件不可見。CommonJS規範規定,每一個模塊內部,module變量表明當前模塊。這個變量是一個對象,它的exports屬性(即module.exports)是對外的接口。加載某個模塊,實際上是加載該模塊的module.exports屬性。CommonJS給出2個工具來實現模塊之間的依賴:
那就先搞一個Hello world的小栗子來試下吧!新建一個項目文件夾吧,雖然項目很小。。。起名commonjs,在裏邊新建2個JavaScript文件,分別命名爲world.js和salute.js,代碼以下:
// salute.js 打招呼 var MySalute = "Hello"; module.exports = MySalute; /*注意上下是分別寫在2個文件js文件裏哦*/ // world.js var MySalute = require("./salute"); var Result = MySalute + " world!"; console.log(Result);
而後無知的我有新建了一個demo.html,(想要在瀏覽器裏打開看看是什麼樣子)內容以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script type="text/javascript" src="world.js"></script> </head> <body> </body> </html>
結果在瀏覽器中打開,查看控制檯大失所望,報了一個錯誤world.js:2 Uncaught ReferenceError: require is not defined
發現瀏覽器不兼容CommonJS的根本緣由,在於缺乏四個Node.js環境的變量:
只要可以提供這四個變量,瀏覽器就能加載 CommonJS 模塊,問題是能夠解決的,可是好像並不怎麼好玩,有興趣的朋友能夠去阮老師博客裏逛逛啊,瀏覽器加載 CommonJS 模塊的原理與實現,裏面還講了Browserify的原理。
這裏使用命令行node world.js
就能夠了。
參考《Node.js開發指南 ByVoid》 Page34
模塊(Module)和包(Package)是 Node.js 最重要的支柱。開發一個具備必定規模的程序不可能只用一個文件,一般須要把各個功能拆分、封裝,而後組合起來,模塊正是爲了實現這種方式而誕生的。在瀏覽器 JavaScript 中,腳本模塊的拆分和組合一般使用 HTML 的script 標籤來實現。Node.js 提供了 require 函數來調用其餘模塊,並且模塊都是基於文件的,機制十分簡單。
Node.js 的模塊和包機制的實現參照了 CommonJS 的標準,但並未徹底遵循。不過二者的區別並不大,通常來講你大可沒必要擔憂,只有當你試圖製做一個除了支持 Node.js以外還要支持其餘平臺的模塊或包的時候才須要仔細研究。一般,二者沒有直接衝突的地方。咱們常常把 Node.js 的模塊和包相提並論,由於模塊和包是沒有本質區別的,兩個概念也時常混用。若是要辨析,那麼能夠把包理解成是實現了某個功能模塊的集合,用於發佈和維護。對使用者來講,模塊和包的區別是透明的,所以常常不做區分。本節中咱們會詳細介紹:
1.什麼是模塊
模塊是 Node.js 應用程序的基本組成部分,文件和模塊是一一對應的。換言之,一個Node.js 文件就是一個模塊,這個文件多是 JavaScript 代碼、JSON 或者編譯過的 C/C++ 擴展。在前面章節的例子中,咱們曾經用到了 var http = require('http')
, 其中 http是 Node.js 的一個核心模塊,其內部是用 C++ 實現的,外部用 JavaScript 封裝。咱們經過require 函數獲取了這個模塊,而後才能使用其中的對象。
2.建立模塊
在 Node.js 中,建立一個模塊很是簡單,由於一個文件就是一個模塊,咱們要關注的問題僅僅在於如何在其餘文件中獲取這個模塊。Node.js 提供了 exports 和 require 兩個對象,其中 exports 是模塊公開的接口, require 用於從外部獲取一個模塊的接口,即所獲取模塊的 exports 對象。讓咱們以一個例子來了解模塊。建立一個 module.js 的文件,內容是:
//module.js var name; exports.setName = function(thyName) { name = thyName; }; exports.sayHello = function() { console.log('Hello ' + name); };
在同一目錄下建立 getmodule.js,內容是:
//getmodule.js var myModule = require('./module'); myModule.setName('BYVoid'); myModule.sayHello();
運行node getmodule.js,結果是:
Hello BYVoid
在以上示例中,module.js 經過 exports 對象把 setName 和 sayHello 做爲模塊的訪問接口,在 getmodule.js 中經過 require('./module')
加載這個模塊,而後就能夠直接訪問 module.js 中 exports 對象的成員函數了。這種接口封裝方式比許多語言要簡潔得多,同時也不失優雅,未引入違反語義的特性,符合傳統的編程邏輯。在這個基礎上,咱們能夠構建大型的應用程序,npm 提供的上萬個模塊都是經過這種簡單的方式搭建起來的。
這裏有個疑問,能夠參考exports 和 module.exports 的區別或module.exports與exports??關於exports的總結
咱們常常看到這樣的寫法:
exports = module.exports = somethings
上面的代碼等價於:
module.exports = somethings
exports = module.exports
原理很簡單,即 module.exports 指向新的對象時,exports 斷開了與 module.exports 的引用,那麼經過 exports = module.exports 讓 exports 從新指向 module.exports 便可。
3.單次加載
上面這個例子有點相似於建立一個對象,但實際上和對象又有本質的區別,由於require 不會重複加載模塊,也就是說不管調用多少次 require, 得到的模塊都是同一個。咱們在 getmodule.js 的基礎上稍做修改:
//loadmodule.js var hello1 = require('./module'); hello1.setName('BYVoid'); var hello2 = require('./module'); hello2.setName('BYVoid 2'); hello1.sayHello();
運行後發現輸出結果是 Hello BYVoid 2 ,這是由於變量 hello1 和 hello2 指向的是同一個實例,所以 hello1.setName 的結果被 hello2.setName 覆蓋,最終輸出結果是由後者決定的。
有時候咱們只是想把一個對象封裝到模塊中,例如:
//singleobject.js function Hello() { var name; this.setName = function (thyName) { name = thyName; }; this.sayHello = function () { console.log('Hello ' + name); }; }; exports.Hello = Hello;
此時咱們在其餘文件中須要經過 require('./singleobject').Hello 來獲取Hello 對象,這略顯冗餘,能夠用下面方法稍微簡化:
//hello.js function Hello() { var name; this.setName = function(thyName) { name = thyName; }; this.sayHello = function() { console.log('Hello ' + name); }; }; module.exports = Hello;
這樣就能夠直接得到這個對象了:
//gethello.js var Hello = require('./hello'); hello = new Hello(); hello.setName('BYVoid'); hello.sayHello();
注意,模塊接口的惟一變化是使用 module.exports = Hello 代替了 exports.Hello=Hello 。在外部引用該模塊時,其接口對象就是要輸出的 Hello 對象自己,而不是原先的exports 。事實上, exports 自己僅僅是一個普通的空對象,即 {} ,它專門用來聲明接口,本質上是經過它爲模塊閉包的內部創建了一個有限的訪問接口。由於它沒有任何特殊的地方,因此能夠用其餘東西來代替,譬如咱們上面例子中的 Hello 對象。
注意,不能夠經過對 exports 直接賦值代替對 module.exports 賦值。exports 實際上只是一個和 module.exports 指向同一個對象的變量,它自己會在模塊執行結束後釋放,但 module 不會,所以只能經過指定module.exports 來改變訪問接口。
5.建立包
包是在模塊基礎上更深一步的抽象,Node.js 的包相似於 C/C++ 的函數庫或者 Java/.Net的類庫。它將某個獨立的功能封裝起來,用於發佈、更新、依賴管理和版本控制。Node.js 根據 CommonJS 規範實現了包機制,開發了 npm來解決包的發佈和獲取需求。Node.js 的包是一個目錄,其中包含一個 JSON 格式的包說明文件 package.json。嚴格符合 CommonJS 規範的包應該具有如下特徵:
package.json 必須在包的頂層目錄下;
二進制文件應該在 bin 目錄下;
JavaScript 代碼應該在 lib 目錄下;
文檔應該在 doc 目錄下;
單元測試應該在 test 目錄下。
Node.js 對包的要求並無這麼嚴格,只要頂層目錄下有 package.json,並符合一些規範便可。固然爲了提升兼容性,咱們仍是建議你在製做包的時候,嚴格遵照 CommonJS 規範。
模塊與文件是一一對應的。文件不只能夠是 JavaScript 代碼或二進制代碼,還能夠是一個文件夾。最簡單的包,就是一個做爲文件夾的模塊。下面咱們來看一個例子,創建一個叫作 somepackage 的文件夾,在其中建立 index.js,內容以下:
//somepackage/index.js exports.hello = function() { console.log('Hello.'); };
而後在 somepackage 以外創建 getpackage.js,內容以下:
//getpackage.js var somePackage = require('./somepackage'); somePackage.hello();
運行 node getpackage.js,控制檯將輸出結果 Hello. 。咱們使用這種方法能夠把文件夾封裝爲一個模塊,即所謂的包。包一般是一些模塊的集合,在模塊的基礎上提供了更高層的抽象,至關於提供了一些固定接口的函數庫。經過定製package.json,咱們能夠建立更復雜、更完善、更符合規範的包用於發佈。
在前面例子中的 somepackage 文件夾下,咱們建立一個叫作 package.json 的文件,內容以下所示:
{ "main" : "./lib/interface.js" }
而後將 index.js 重命名爲 interface.js 並放入 lib 子文件夾下。以一樣的方式再次調用這個包,依然能夠正常使用。Node.js 在調用某個包時,會首先檢查包中 package.json 文件的 main 字段,將其做爲包的接口模塊,若是 package.json 或 main 字段不存在,會嘗試尋找 index.js 或 index.node 做爲包的接口。
package.json 是 CommonJS 規定的用來描述包的文件,徹底符合規範的 package.json 文件應該含有如下字段。
name :包的名稱,必須是惟一的,由小寫英文字母、數字和下劃線組成,不能包含空格。
description :包的簡要說明。
version :符合語義化版本識別規範的版本字符串。
keywords :關鍵字數組,一般用於搜索。
maintainers :維護者數組,每一個元素要包含 name 、 email (可選)、 web (可選)字段。
contributors :貢獻者數組,格式與 maintainers 相同。包的做者應該是貢獻者數組的第一個元素。
bugs :提交bug的地址,能夠是網址或者電子郵件地址。
licenses :許可證數組,每一個元素要包含 type (許可證的名稱)和 url (連接到許可證文本的地址)字段。
repositories :倉庫託管地址數組,每一個元素要包含 type (倉庫的類型,如 git )、url (倉庫的地址)和 path (相對於倉庫的路徑,可選)字段。
dependencies :包的依賴,一個關聯數組,由包名稱和版本號組成。
下面是一個徹底符合 CommonJS 規範的 package.json 示例:
{ "name": "mypackage", "description": "Sample package for CommonJS. This package demonstrates the required elements of a CommonJS package.", "version": "0.7.0", "keywords": ["package", "example"], "maintainers": [{ "name": "Bill Smith", "email": "bills@example.com", }], "contributors": [{ "name": "BYVoid", "web": "http://www.byvoid.com/" }], "bugs": { "mail": "dev@example.com", "web": "http://www.example.com/bugs" }, "licenses": [{ "type": "GPLv2", "url": "http://www.example.org/licenses/gpl.html" }], "repositories": [{ "type": "git", "url": "http://github.com/BYVoid/mypackage.git" }], "dependencies": { "webkit": "1.2", "ssl": { "gnutls": ["1.0", "2.0"], "openssl": "0.9.8" } } }
參考 《Node.js開發指南 ByVoid》Page 132
Node.js 的模塊能夠分爲兩大類,一類是核心模塊,另外一類是文件模塊。核心模塊就是Node.js 標準 API 中提供的模塊,如 fs 、 http 、 net 、 vm 等,這些都是由 Node.js 官方提供的模塊,編譯成了二進制代碼。咱們能夠直接經過 require 獲取核心模塊,例如require('fs') 。核心模塊擁有最高的加載優先級,換言之若是有模塊與其命名衝突,Node.js 老是會加載核心模塊。文件模塊則是存儲爲單獨的文件(或文件夾)的模塊,多是 JavaScript 代碼、JSON 或編譯好的 C/C++ 代碼。文件模塊的加載方法相對複雜,但十分靈活,尤爲是和 npm 結合使用時。在不顯式指定文件模塊擴展名的時候,Node.js 會分別試圖加上.js、.json 和 .node擴展名。.js 是 JavaScript 代碼,.json 是 JSON 格式的文本,.node 是編譯好的 C/C++ 代碼。
文件模塊的加載有兩種方式,一種是按路徑加載,一種是查找 node_modules 文件夾。若是 require 參數以「 / 」開頭,那麼就以絕對路徑的方式查找模塊名稱,例如 require('/home/byvoid/module') 將會按照優先級依次嘗試加載 /home/byvoid/module.js、/home/byvoid/module.json 和 /home/byvoid/module.node。若是 require 參數以「 ./ 」或「 ../ 」開頭,那麼則以相對路徑的方式來查找模塊,這種方式在應用中是最多見的。例如前面的例子中咱們用了 require('./hello') 來加載同一文件夾下的hello.js。
若是 require 參數不以「 / 」、「 ./ 」或「 ../ 」開頭,而該模塊又不是核心模塊,那麼就要經過查找 node_modules 加載模塊了。咱們使用npm獲取的包一般就是以這種方式加載的。在某個目錄下執行命令 npm install express,你會發現出現了一個叫作node_modules的目錄,裏面的結構大概如圖 6-1 所示。
在 node_modules 目錄的外面一層,咱們能夠直接使用 require('express') 來代替require('./node_modules/express') 。這是Node.js模塊加載的一個重要特性:經過查找 node_modules 目錄來加載模塊。當 require 遇到一個既不是核心模塊,又不是以路徑形式表示的模塊名稱時,會試圖在當前目錄下的 node_modules 目錄中來查找是否是有這樣一個模塊。若是沒有找到,則會在當前目錄的上一層中的 node_modules 目錄中繼續查找,反覆執行這一過程,直到遇到根目錄爲止。舉個例子,咱們要在 /home/byvoid/develop/foo.js 中使用 require('bar.js') 命令,Node.js會依次查找:
/home/byvoid/develop/node_modules/bar.js
/home/byvoid/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js
爲何要這樣作呢?由於一般一個工程內會有一些子目錄,當子目錄內的文件須要訪問到工程共同依賴的模塊時,就須要向父目錄上溯了。好比說工程的目錄結構以下:
|- project |- app.js |- models |- ... |- views |- ... |- controllers |- index_controller.js |- error_controller.js |- ... |- node_modules |- express
咱們不只要在 project 目錄下的 app.js 中使用 require('express') ,並且可能要在controllers 子目錄下的index_controller.js 中也使用 require('express') ,這時就須要向父目錄上溯一層才能找到 node_modules 中的 express 了。