前端模塊化(一)nodeJS中的CommonJS範例

序文

模塊化,人人用vuereact等東西,都邑接觸到像exportsmodule.exportsexportexport defaultrequiredefineimport等等字段,覺得很多人關於這些東西照樣分不清,觀點異常的隱約,便想着寫這麼一篇文章,一是協助本身梳理學問點,二是跟人人一同生長。其中有寫得不對的,請實時提出來 ,我實時改正。

剛開始寫的時刻有些無從下手,一是因為學問點太多,二是因為本身的履歷還不足以協助人人從深層次理會js的模塊化中的辨別,以及其完成道理、頭腦。這是一篇本身的進修筆記整頓,我只能帶人人相識前端模塊化,辨別他們並正確的運用他們。

先給人人扔出幾條學問:

  • CommonJSNodeJS模塊體系細緻完成的基石。
  • AMD:異步模塊範例,是RequireJS在推行過程當中對模塊定義的範例化產出的,推重依靠前置;
  • UMD:兼容AMDcommonJS範例的同時,還兼容全局援用的體式格局;
  • CMD:是SeaJS 在推行過程當中對模塊定義的範例化產出的,推重依靠就近;
  • ES6:ES6模塊的設想頭腦是只管的靜態化,使得編譯時就可以肯定模塊的依靠關聯,以及輸入和輸出的變量;

CommonJS範例

CommonJS官網上寫道,它願望js不單單議可以在瀏覽器上運轉,而是可以在任何地方運轉,使其具有開闢大型運用的才。

javascript: not just for browsers any more!

CommonJS定義的模塊分為:

  1. 模塊援用(require)
  2. 模塊定義(exports)
  3. 模塊標識(module)

他可以做到:

  • 服務器端JavaScript運用程序
  • 敕令行東西
  • 圖形界面運用程序
  • 夾雜運用程序(如,Titanium或Adobe AIR)

CommonJS模塊的特性以下

  • 一切代碼都運轉在模塊作用域,不會污染全局作用域。
  • 模塊可以屢次加載,然則只會在第一次加載時運轉一次,然後運轉效果就被緩存了,今後再加載,就直接讀取緩存效果。要想讓模塊再次運轉,必需消滅緩存。
  • 模塊加載的遞次,依據其在代碼中湧現的遞次。

先談一談包的觀點

前面給人人說過,node.js是基於CommonJS的範例完成的,NPM人人肯定都很熟習,它實踐了CommonJS的包範例。

包範例

關於包範例,類比於git堆棧,我們可以這麼明白:

  • git init在當前文件夾中生成了隱蔽文件.git,我們把它叫做git堆棧
  • npm init敕令在當前文件夾中生成了配置文件package.json,它形貌了當前這個包,我們管這個文件叫做包(觀點不正確,可以這麼明白)。

包構造

嚴厲依據CommonJS範例來的話,包的目次應該包含以下文件或目次。

  • package.json:包形貌文件,存在於包頂級目次下
  • bin:寄存可實行二進制文件的目次
  • lib:寄存js代碼的目次
  • doc:寄存文檔的目次
  • test:寄存單元測試用例代碼的目次

package.json則是一個配置文件,它形貌了包的相干信息。

NodeJS模塊

既然node.js是基於CommonJS完成的,那末我們先來簡樸看看NodeJS的模塊道理。

近來參加了公司展開的一次培訓,構造性頭腦造就。任何東西都可以舉行分類,事物一旦舉行分類,更利於人人對此事物的認知,也能輕易人人影象。所以我們先來看看Node的模塊分類🐵。

一般分類

先給人人講講模塊的分類

  • 中心模塊

    • 中心模塊指的是那些被編譯進Node的二進制模塊
    • 預置在Node中,供應Node的基本功能,如fs、http、https等。
    • 中心模塊運用C/C++完成,外部運用JS封裝
  • 第三方模塊

    • Node運用NPM(Node Package Manager)裝置第三方模塊
    • NPM會將模塊裝置(可以說是下載到)到運用根目次下的node_modules文件夾中
    • 模塊加載時,node會先在中心模塊文件夾中舉行搜刮,然後再到node_modules文件夾中舉行搜刮
  • 文件模塊

    • 文件可放在任何位置
    • 加載模塊文件時加上途徑即可
  • 文件夾模塊(後續的nodeJS的加載劃定規矩將會細緻引見)

    • Node起首會在該文件夾中搜刮package.json文件,

      • 存在,Node便嘗試剖析它,並加載main屬性指定的模塊文件
      • 不存在(或許package.json沒有定義main屬性),Node默許加載該文件夾下的index.js文件(main屬性實在NodeJS的一個拓展,CommonJS規範定義中實在並不包含此字段)

預計人人關於文件夾模塊觀點都比較隱約,它實在相當於一個自定義模塊,給人人舉一個栗子🤡:

在根目次下的/main.js中,我們須要運用一個自定義文件夾模塊。我們將一切的自定義文件夾模塊寄存在根目次下的/module下,其中有一個/module/demo文件夾,是我們須要引入的文件夾模塊;

|—— main.js
|—— module
    |—— demo
        |—— package.json
        |—— demo.js

package.json文件的信息以下:

{
    "name": "demo",
    "version": "1.0.0",
    "main": "./demo.js"
}

/main.js中:

let demo = require("./modules/demo");

此時,Node將會依據package.json中指定的main屬性,去加載./modules/demo/demo.js;

這就是一個最簡樸的包,以一個文件夾作為一個模塊。

nodeJS模塊與CommonJS

module屬性
  • module.id 模塊的辨認符,一般是帶有絕對途徑的模塊文件名。
  • module.filename 模塊的文件名,帶有絕對途徑。
  • module.loaded 返回一個布爾值,示意模塊是不是已完成加載。
  • module.parent 返回一個對象,示意挪用該模塊的模塊。
  • module.children 返回一個數組,示意該模塊要用到的其他模塊。
  • module.exports 示意模塊對外輸出的值。

來做一個測試,看看module究竟是個什麼東西(寫的細緻些,程度高的自行濾過);

  1. 新建一個文件夾,名為modulePractice
  2. 敕令行進入cd modulePractive/文件夾
  3. npm init,輸入信息,此時我們相當於建立了一個包
  4. npm install jquery,裝置jquery來做測試
  5. 新建modulePractice/test.js
|—— modulePractice
    |—— node_module
    |—— package.json
    |—— test.js
// test.js
var jquery = require('jquery');
exports.$ = jquery;
console.log(module);        //module就是當前模塊內部中的一個對象,代表當前對象

終端實行這個文件

node test.js

敕令行會輸出以下信息:

Module {
  id: '.',
  exports: { '$': [Function] },
  parent: null,
  filename: '/Applications/practice/nodepractice/modulePratice/test.js',
  loaded: false,
  children:
   [ Module {
       id: '/Applications/practice/nodepractice/modulePratice/node_modules/jquery/dist/jquery.js',
       exports: [Function],
       parent: [Circular],
       filename: '/Applications/practice/nodepractice/modulePratice/node_modules/jquery/dist/jquery.js',
       loaded: true,
       children: [],
       paths: [Array] } ],
  paths:
   [ '/Applications/practice/nodepractice/modulePratice/node_modules',
     '/Applications/practice/nodepractice/node_modules',
     '/Applications/practice/node_modules',
     '/Applications/node_modules',
     '/node_modules' ] }

如今我們可以看到,當前這個模塊的parent屬性為null,這證實當前這個模塊是一個進口劇本。

我們來看看在test.js中引入別的文件模塊,module會輸出什麼

6.新建一個modulePractice/child.js

|—— modulePractice
    |—— node_module
    |—— package.json
    |—— test.js
    |—— child.js
//child.js
var str = "I'm child";
exports.str = str;
console.log(module);

再一次實行:

node test.js

我們再來離別看看child.js中的moduletest.js中的module離別是什麼模樣

//這個是child.js中輸出的信息
Module {
  id: '/Applications/practice/nodepractice/modulePratice/child.js',
  exports: { str: 'I\'m child' },
  parent:
   Module {
     id: '.',
     exports: {},
     parent: null,
     filename: '/Applications/practice/nodepractice/modulePratice/test.js',
     loaded: false,
     children: [ [Circular] ],
     paths:
      [ '/Applications/practice/nodepractice/modulePratice/node_modules',
        '/Applications/practice/nodepractice/node_modules',
        '/Applications/practice/node_modules',
        '/Applications/node_modules',
        '/node_modules' ] },
  filename: '/Applications/practice/nodepractice/modulePratice/child.js',
  loaded: false,
  children: [],
  paths:
   [ '/Applications/practice/nodepractice/modulePratice/node_modules',
     '/Applications/practice/nodepractice/node_modules',
     '/Applications/practice/node_modules',
     '/Applications/node_modules',
     '/node_modules' ] }

//這個是test.js中輸出的module信息
Module {
  id: '.',
  exports: { '$': [Function] },
  parent: null,
  filename: '/Applications/practice/nodepractice/modulePratice/test.js',
  loaded: false,
  children:
   [ Module {
       id: '/Applications/practice/nodepractice/modulePratice/child.js',
       exports: [Object],
       parent: [Circular],
       filename: '/Applications/practice/nodepractice/modulePratice/child.js',
       loaded: true,
       children: [],
       paths: [Array] },
     Module {
       id: '/Applications/practice/nodepractice/modulePratice/node_modules/jquery/dist/jquery.js',
       exports: [Function],
       parent: [Circular],
       filename: '/Applications/practice/nodepractice/modulePratice/node_modules/jquery/dist/jquery.js',
       loaded: true,
       children: [],
       paths: [Array] } ],
  paths:
   [ '/Applications/practice/nodepractice/modulePratice/node_modules',
     '/Applications/practice/nodepractice/node_modules',
     '/Applications/practice/node_modules',
     '/Applications/node_modules',
     '/node_modules' ] }

人人可以看到

  • child.js中的parent屬性輸出的是test.jsmodule信息,
  • test.js中的children屬性,包含了jquerychild.js兩個module信息
  • test.js中的parent屬性為null

由此,我們可以以module.parent來推斷當前模塊是不是是進口劇本

固然,也有別的要領可以推斷進口劇本,比方運用require.main

child.js修正以下:

//child.js
var str = "I'm child";
exports.str = str;
console.log(require.main);
node test.js
Module {
  id: '.',
  exports: {},
  parent: null,
  filename: '/Applications/practice/nodepractice/modulePratice/test.js',
  loaded: false,
  children:
   [ Module {
       id: '/Applications/practice/nodepractice/modulePratice/child.js',
       exports: [Object],
       parent: [Circular],
       filename: '/Applications/practice/nodepractice/modulePratice/child.js',
       loaded: false,
       children: [],
       paths: [Array] } ],
  paths:
   [ '/Applications/practice/nodepractice/modulePratice/node_modules',
     '/Applications/practice/nodepractice/node_modules',
     '/Applications/practice/node_modules',
     '/Applications/node_modules',
     '/node_modules' ] }

可以看到,require.main直接輸出的是進口劇本,因為我們是在child.js中打印的require.main,所以我們拿不到test.js這個進口劇本的exports,且只能看到當前進口劇本的children唯一child.js一個模塊;

換一種體式格局舉行測試,我們在test.js中打印require.main看一下會輸出什麼東西;

test.js修正以下:

var child = require("./child.js");
var jquery = require('jquery');
exports.$ = jquery;
console.log(require.main);

實行

node test.js

拿到以下信息:

Module {
  id: '.',
  exports: { '$': [Function] },
  parent: null,
  filename: '/Applications/practice/nodepractice/modulePratice/test.js',
  loaded: false,
  children:
   [ Module {
       id: '/Applications/practice/nodepractice/modulePratice/child.js',
       exports: [Object],
       parent: [Circular],
       filename: '/Applications/practice/nodepractice/modulePratice/child.js',
       loaded: true,
       children: [],
       paths: [Array] },
     Module {
       id: '/Applications/practice/nodepractice/modulePratice/node_modules/jquery/dist/jquery.js',
       exports: [Function],
       parent: [Circular],
       filename: '/Applications/practice/nodepractice/modulePratice/node_modules/jquery/dist/jquery.js',
       loaded: true,
       children: [],
       paths: [Array] } ],
  paths:
   [ '/Applications/practice/nodepractice/modulePratice/node_modules',
     '/Applications/practice/nodepractice/node_modules',
     '/Applications/practice/node_modules',
     '/Applications/node_modules',
     '/node_modules' ] }

也就是說,在真正的進口文件中,打印的require.main信息,才是完整的信息;

一樣也可以用require.main輸出的module信息中的parent屬性,來推斷是不是是進口劇本;

固然也可以在當前模塊中推斷require.main === module,若為真,則代表它是被直接實行的(node xxx.js

exports屬性

如今我們相識了module屬性,那末module.exportsexports都是什麼呢?

從以上的測試,我們可以看到,module中實在帶有的exports屬性,就是我們對外的接口。也就是說,module.exports屬性示意當前模塊對外輸出的接口,其他文件加載該模塊,實際上就是讀取module.exports變量。

exports變量,實際上是nodeJS為了輕易,為每一個模塊供應一個exports變量,指向module.exports。這等同在每一個模塊頭部,有一行如許的敕令。

var exports = module.exports;

因而,我們可以直接向exports對象增加要領

exports.area = function (r) {
  return Math.PI * r * r;
};

exports.circumference = function (r) {
  return 2 * Math.PI * r;
};

注重點:

  • 不能直接將exports變量指向一個,即是切斷了exportsmodule.exports的聯絡,他將不再是一個接口,而僅僅當前模塊中的一個局部變量。此時你在當前模塊中寫的一切其他的exports導出的接口,都將失效。而只需module.exports可以暴露出去當前模塊的對外接口。

實在說簡樸點,nodeJS僅僅為了輕易,用了一個變量exports直接指向了module.exports了,你只需注重exports變量,正確指向module.exports屬性即可。終究我們導出去的接口,就是module.exports屬性。

加載劃定規矩,require要領

require敕令的基本功能是,讀入並實行一個JavaScript文件,然後返回該模塊的exports對象。假如沒有發明指定模塊,會報錯。

require敕令是CommonJS範例當中,用來加載其他模塊的敕令。它實在不是一個全局敕令,而是指向當前模塊的module.require敕令,而後者又挪用Node的內部敕令Module._load

  • require(): 加載外部模塊
  • require.resolve():將模塊名剖析到一個絕對途徑
  • require.main:指向主模塊
  • require.cache:指向一切緩存的模塊
  • require.extensions:依據文件的後綴名,挪用差別的實行函數

require敕令用於加載文件,後綴名默許為.js。

var foo = require('foo');
//  等同於
var foo = require('foo.js');

而這類體式格局的引入(不是絕對途徑,且不是相對途徑),將會以以下劃定規矩舉行搜刮加載;

/usr/local/lib/node/foo.js
/home/user/projects/node_modules/foo.js
/home/user/node_modules/foo.js
/home/node_modules/foo.js
/node_modules/foo.js

也就是說,將會先搜刮默許的中心模塊(node),再層級往上找node_modules中的當前模塊。如許使得差別的模塊可以將所依靠的模塊當地化。

而假如是一個:

require('example-module/path/to/file')
  1. 則將先找到example-module的位置,然後再以它為參數,找到後續途徑。
  2. 查找是不是有file文件夾

    • 若找到,則嘗試找package.json,並以其main屬性指定的目次作為進口文件,不然便以當前目次下的index.js | index.node作為進口文件
    • 若未找到,則Node會嘗試為文件名增加.js.json.node后,再去搜刮。.js件會以文本花樣的JavaScript劇本文件剖析,.json文件會以JSON花樣的文本文件剖析,.node文件會以編譯后的二進制文件剖析。
  3. 若還沒有發明發明,則報錯。

第一次加載某個模塊時,Node會緩存該模塊。今後再加載該模塊,就直接從緩存掏出該模塊的module.exports屬性。

CommonJS模塊載入體式格局

CommonJS範例加載模塊是同步的,也就是說,只需加載完成,才實行背面的操縱。所以一般來說,CommonJS範例不適用於瀏覽器環境。但是,對服務器端不是一個題目,因為一切的模塊都寄存在當地硬盤,可以同步加載完成,等待時候就是硬盤的讀取時候。然則,關於瀏覽器,這倒是一個大題目,因為模塊都放在服務器端,等待時候取決於網速的快慢,能夠要等很長時候,瀏覽器處於”假死”狀況。

因而,瀏覽器端的模塊,不能採納”同步加載”(synchronous),只能採納”異步加載”(asynchronous)。這就是AMD範例降生的背景。

下一章將會給人人講一下AMD範例。

參考文章

    原文作者:John
    原文地址: https://segmentfault.com/a/1190000015337690
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞