序文
模塊化,人人用vue
,react
等東西,都邑接觸到像exports
,module.exports
,export
,export default
,require
,define
,import
等等字段,覺得很多人關於這些東西照樣分不清,觀點異常的隱約,便想着寫這麼一篇文章,一是協助本身梳理學問點,二是跟人人一同生長。其中有寫得不對的,請實時提出來 ,我實時改正。
剛開始寫的時刻有些無從下手,一是因為學問點太多,二是因為本身的履歷還不足以協助人人從深層次理會js的模塊化中的辨別,以及其完成道理、頭腦。這是一篇本身的進修筆記整頓,我只能帶人人相識前端模塊化,辨別他們並正確的運用他們。
先給人人扔出幾條學問:
-
CommonJS
:NodeJS
模塊體系細緻完成的基石。 -
AMD
:異步模塊範例,是RequireJS
在推行過程當中對模塊定義的範例化產出的,推重依靠前置; -
UMD
:兼容AMD
和commonJS
範例的同時,還兼容全局援用的體式格局; -
CMD
:是SeaJS
在推行過程當中對模塊定義的範例化產出的,推重依靠就近; -
ES6
:ES6模塊的設想頭腦是只管的靜態化,使得編譯時就可以肯定模塊的依靠關聯,以及輸入和輸出的變量;
CommonJS範例
CommonJS官網上寫道,它願望js不單單議可以在瀏覽器上運轉,而是可以在任何地方運轉,使其具有開闢大型運用的才。
javascript: not just for browsers any more!
CommonJS定義的模塊分為:
- 模塊援用(
require
) - 模塊定義(
exports
) - 模塊標識(
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
究竟是個什麼東西(寫的細緻些,程度高的自行濾過);
- 新建一個文件夾,名為
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
)
exports屬性
如今我們相識了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要領
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範例加載模塊是同步的,也就是說,只需加載完成,才實行背面的操縱。所以一般來說,CommonJS範例不適用於瀏覽器環境。但是,對服務器端不是一個題目,因為一切的模塊都寄存在當地硬盤,可以同步加載完成,等待時候就是硬盤的讀取時候。然則,關於瀏覽器,這倒是一個大題目,因為模塊都放在服務器端,等待時候取決於網速的快慢,能夠要等很長時候,瀏覽器處於”假死”狀況。
因而,瀏覽器端的模塊,不能採納”同步加載”(synchronous
),只能採納”異步加載”(asynchronous
)。這就是AMD範例降生的背景。
下一章將會給人人講一下AMD
範例。