模塊化,你們用vue
,react
等東西,都會接觸到像exports
,module.exports
,export
,export default
,require
,define
,import
等等字段,感受不少人對於這些東西仍是分不清,概念很是的模糊,便想着寫這麼一篇文章,一是幫助本身梳理知識點,二是跟你們一塊兒成長。其中有寫得不對的,請及時提出來 ,我及時更正。javascript
剛開始寫的時候有些無從下手,一是由於知識點太多,二是由於本身的經驗還不足以幫助你們從深層次剖析js的模塊化中的區別,以及其實現原理、思想。這是一篇本身的學習筆記整理,我只能帶你們瞭解前端模塊化,區分他們並正確的使用他們。html
先給你們扔出幾條知識:前端
CommonJS
:NodeJS
模塊系統具體實現的基石。AMD
:異步模塊規範,是RequireJS
在推廣過程當中對模塊定義的規範化產出的,推崇依賴前置;UMD
:兼容AMD
和commonJS
規範的同時,還兼容全局引用的方式;CMD
:是SeaJS
在推廣過程當中對模塊定義的規範化產出的,推崇依賴就近;ES6
:ES6模塊的設計思想是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量;CommonJS官網上寫道,它但願js不只僅能夠在瀏覽器上運行,而是能夠在任何地方運行,使其具有開發大型應用的能力。vue
javascript: not just for browsers any more!
CommonJS定義的模塊分爲:java
require
)exports
)module
)他能夠作到:node
CommonJS模塊的特色以下react
前面給你們說過,node.js
是基於CommonJS
的規範實現的,NPM
你們必定都很熟悉,它實踐了CommonJS
的包規範。jquery
關於包規範,類比於git
倉庫,咱們能夠這麼理解:git
git init
在當前文件夾中生成了隱藏文件.git
,咱們把它叫作git倉庫
。npm init
命令在當前文件夾中生成了配置文件package.json
,它描述了當前這個包,咱們管這個文件叫作包(概念不許確,能夠這麼理解)。嚴格按照CommonJS
規範來的話,包的目錄應當包含如下文件或目錄。npm
package.json
:包描述文件,存在於包頂級目錄下bin
:存放可執行二進制文件的目錄lib
:存放js代碼的目錄doc
:存放文檔的目錄test
:存放單元測試用例代碼的目錄而package.json
則是一個配置文件,它描述了包的相關信息。
既然node.js
是基於CommonJS
實現的,那麼咱們先來簡單看看NodeJS
的模塊原理。
最近參加了公司開展的一次培訓,結構性思惟培養。任何東西都可以進行分類,事物一旦進行分類,更利於你們對此事物的認知,也能方便你們記憶。因此咱們先來看看Node
的模塊分類🐵。
先給你們講講模塊的分類
核心模塊
第三方模塊
Node
使用NPM
(Node Package Manager
)安裝第三方模塊NPM
會將模塊安裝(能夠說是下載到)到應用根目錄下的node_modules
文件夾中node
會先在覈心模塊文件夾中進行搜索,而後再到node_modules
文件夾中進行搜索文件模塊
文件夾模塊(後續的nodeJS的加載規則
將會詳細介紹)
Node
首先會在該文件夾中搜索package.json
文件,
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
;
這就是一個最簡單的包,以一個文件夾做爲一個模塊。
module.id
模塊的識別符,一般是帶有絕對路徑的模塊文件名。module.filename
模塊的文件名,帶有絕對路徑。module.loaded
返回一個布爾值,表示模塊是否已經完成加載。module.parent
返回一個對象,表示調用該模塊的模塊。module.children
返回一個數組,表示該模塊要用到的其餘模塊。module.exports
表示模塊對外輸出的值。來作一個測試,看看module
究竟是個什麼東西(寫的詳細些,水平高的自行濾過);
modulePractice
cd modulePractive/
文件夾npm init
,輸入信息,此時咱們至關於創建了一個包npm install jquery
,安裝jquery
來作測試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
中的module
和test.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.js
的module
信息,test.js
中的children
屬性,包括了jquery
和child.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
)
如今咱們瞭解了module
屬性,那麼module.exports
和exports
都是什麼呢?
從以上的測試,咱們能夠看到,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
變量指向一個值,等於切斷了exports
與module.exports
的聯繫,他將再也不是一個接口,而僅僅當前模塊中的一個局部變量。此時你在當前模塊中寫的全部其餘的exports
導出的接口,都將失效。而只有module.exports
可以暴露出去當前模塊的對外接口。其實說簡單點,nodeJS
僅僅爲了方便,用了一個變量exports
直接指向了module.exports
了,你只要注意exports
變量,正確指向module.exports
屬性便可。最終咱們導出去的接口,就是module.exports
屬性。
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')
example-module
的位置,而後再以它爲參數,找到後續路徑。查找是否有file
文件夾
package.json
,並以其main
屬性指定的目錄做爲入口文件,不然便以當前目錄下的index.js | index.node
做爲入口文件Node
會嘗試爲文件名添加.js
、.json
、.node
後,再去搜索。.js
件會以文本格式的JavaScript
腳本文件解析,.json
文件會以JSON
格式的文本文件解析,.node
文件會以編譯後的二進制文件解析。第一次加載某個模塊時,Node會緩存該模塊。之後再加載該模塊,就直接從緩存取出該模塊的module.exports屬性。
CommonJS規範加載模塊是同步的,也就是說,只有加載完成,才能執行後面的操做。因此通常來講,CommonJS規範不適用於瀏覽器環境。然而,對服務器端不是一個問題,由於全部的模塊都存放在本地硬盤,能夠同步加載完成,等待時間就是硬盤的讀取時間。可是,對於瀏覽器,這倒是一個大問題,由於模塊都放在服務器端,等待時間取決於網速的快慢,可能要等很長時間,瀏覽器處於"假死"狀態。
所以,瀏覽器端的模塊,不能採用"同步加載"(synchronous
),只能採用"異步加載"(asynchronous
)。這就是AMD規範誕生的背景。
下一章將會給你們講一下AMD
規範。