JavaScript模塊化演變 CommonJs,AMD, CMD, UMD(一)

原文連接: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

1、原始寫法

模塊就是實現特定功能的一組方法。只要把不一樣的函數(以及記錄狀態的變量)簡單地放在一塊兒,就算是一個模塊。git

  function m1(){
    //...
  }
  function m2(){
    //...
  }

上面的函數m1()和m2(),組成一個模塊。使用的時候,直接調用就好了。這種作法的缺點很明顯:"污染"了全局變量,沒法保證不與其餘模塊發生變量名衝突,並且模塊成員之間看不出直接關係。程序員

2、對象寫法

爲了解決上面的缺點,能夠把模塊寫成一個對象,全部的模塊成員都放到這個對象裏面。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();
在沒有相似 AMD 這樣的模塊化的格式和規範時,在前端的 JavaScript 中,命名空間是是一個很是棒的實踐,用於避免全局變量污染以及用於組織代碼模塊的邏輯性,擴展性,可讀性和可維護性。
 
一般選擇一個頂級的應用命名空間而且這個命名空間(即對象)是惟一一個掛載到全局對象上的對象(在瀏覽器中是 window,在 Node.js 應用中是 global)。當選擇了應用的頂級命名空間,嵌套命名空間可被用於一個模塊化的架構。
例如,考慮下面:
var myMasterNS = myMasterNS || {};
myMasterNS.mySubNS = myMasterNS.mySubNS || {};
myMasterNS.mySubNS.someFunction = function(){
 //插入邏輯 
};

這裏咱們聲明瞭一個名爲 myMasterNS 的簡單全局對象,又分配了一個子命名空間對象做爲原來命名空間的一個屬性,這個例子中是 mySubNS。如今咱們可以在頂級命名空間下實現任何所需的功能,而且任何後面的子命名空間都不會污染全局做用域。請注意,這不是使用 JavaScript 實現命名空間的惟一模式。我在這個例子中選擇它,是由於這種模式的簡單和可讀性。

3、當即執行函數寫法

使用"當即執行函數"(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模塊的基本寫法。下面,再對這種寫法進行加工。

4、放大模式

若是一個模塊很大,必須分紅幾個部分,或者一個模塊須要繼承另外一個模塊,這時就有必要採用"放大模式"(augmentation)。

  var module1 = (function (mod){
    mod.m3 = function () {
      //...
    };
    return mod;
  })(module1);

上面的代碼爲module1模塊添加了一個新方法m3(),而後返回新的module1模塊。

5、寬放大模式(Loose augmentation)

在瀏覽器環境中,模塊的各個部分一般都是從網上獲取的,有時沒法知道哪一個部分會先加載。若是採用上一節的寫法,第一個執行的部分有可能加載一個不存在空對象,這時就要採用"寬放大模式"。

  var module1 = ( function (mod){
    //...
    return mod;
  })(window.module1 || {});

 

與"放大模式"相比,"寬放大模式"就是"當即執行函數"的參數能夠是空對象。

6、輸入全局變量

獨立性是模塊的重要特色,模塊內部最好不與程序的其餘部分直接交互。爲了在模塊內部調用全局變量,必須顯式地將其餘變量輸入模塊。

  var module1 = (function ($, YAHOO) {
    //...

  })(jQuery, YAHOO);

上面的module1模塊須要使用jQuery庫和YUI庫,就把這兩個庫(實際上是兩個模塊)看成參數輸入module1。這樣作除了保證模塊的獨立性,還使得模塊之間的依賴關係變得明顯。這方面更多的討論,參見Ben Cherry的著名文章《JavaScript Module Pattern: In-Depth》

7、模塊的規範

先想想,爲何模塊很重要?由於有了模塊,咱們就能夠更方便地使用別人的代碼,想要什麼功能,就加載什麼模塊。可是,這樣作有一個前提,那就是你們必須以一樣的方式編寫模塊,不然你有你的寫法,我有個人寫法,豈不是亂了套!考慮到Javascript模塊如今尚未官方規範,這一點就更重要了。目前,通行的Javascript模塊規範共有兩種:CommonJSAMD。我主要介紹AMD,可是要先從CommonJS講起。

8、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後文詳述

9、瀏覽器環境

有了服務器端模塊之後,很天然地,你們就想要客戶端模塊。並且最好二者可以兼容,一個模塊不用修改,在服務器和瀏覽器均可以運行。可是,因爲一個重大的侷限,使得CommonJS規範不適用於瀏覽器環境。仍是上一節的代碼,若是在瀏覽器中運行,會有一個很大的問題,你能看出來嗎?

  var math = require('math');

  math.add(2, 3);

第二行math.add(2, 3),在第一行require('math')以後運行,所以必須等math.js加載完成。也就是說,若是加載時間很長,整個應用就會停在那裏等。這對服務器端不是一個問題,由於全部的模塊都存放在本地硬盤,能夠同步加載完成,等待時間就是硬盤的讀取時間。可是,對於瀏覽器,這倒是一個大問題,由於模塊都放在服務器端,等待時間取決於網速的快慢,可能要等很長時間,瀏覽器處於"假死"狀態。所以,瀏覽器端的模塊,不能採用"同步加載"(synchronous),只能採用"異步加載"(asynchronous)。這就是AMD規範誕生的背景。

10、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.jscurl.js。本系列的第三部分,將經過介紹require.js,進一步講解AMD的用法,以及如何將模塊化編程投入實戰。

11、爲何要用require.js?

最先的時候,全部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)管理模塊之間的依賴性,便於代碼的編寫和維護。

12、require.js的加載

使用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。

十3、require.js主模塊的寫法

上一節的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

十4、CMD

參考
淺析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的一點,犧牲性能來帶來開發的便利性,實際上解析模塊用的時間短到能夠忽略。

十5、UMD

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 ...
});

 

十6、JavaScript模塊化編程探索

CommonJS團隊定義了module格式來解決JavaScript做用域問題,這樣確保了每個module都在本身的命名空間下執行。根據CommonJS的規範,每一個文件就是一個模塊,有本身的做用域。在一個文件裏面定義的變量、函數、類,都是私有的,對其餘文件不可見。CommonJS規範規定,每一個模塊內部,module變量表明當前模塊。這個變量是一個對象,它的exports屬性(即module.exports)是對外的接口。加載某個模塊,實際上是加載該模塊的module.exports屬性。CommonJS給出2個工具來實現模塊之間的依賴:

  • require() 用於在當前做用域引入已有的模塊
  • module object 用於從當前做用域導出一些東東

那就先搞一個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環境的變量:

  • module
  • exports
  • require
  • global

只要可以提供這四個變量,瀏覽器就能加載 CommonJS 模塊,問題是能夠解決的,可是好像並不怎麼好玩,有興趣的朋友能夠去阮老師博客裏逛逛啊,瀏覽器加載 CommonJS 模塊的原理與實現,裏面還講了Browserify的原理。

這裏使用命令行node world.js就能夠了。

十7、Node.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的總結

  • module.exports 初始值爲一個空對象 {}
  • exports 是指向的 module.exports 的引用
  • require() 返回的是 module.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 覆蓋,最終輸出結果是由後者決定的。

 

  1. 覆蓋 exports

有時候咱們只是想把一個對象封裝到模塊中,例如:

//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"
        }
    }
}
十6、Node.js模塊加載機制

參考 《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 所示。


 
圖6-1 node_modules 目錄結構

在 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 了。

做者:合肥懶皮 連接:https://www.jianshu.com/p/33d53cce8237 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出"web": "http://www.byvoid.com/"
相關文章
相關標籤/搜索