這塊標準是在 2009 年提出來的,包含模塊、IO、文件等。通過 Node.js 採用並作調整,因此提及 CommonJS 一般是 Node.js 中的版本了。在使用 Node.js 包管理器的年代,CommonJs 成爲一顆有流量的明星了。webpack
CommonJs 的模塊自然有自身的做用域,全部變量和函數聲明只能本身訪問,外人想都別想,這個保護機制太 nice 了。git
// order.js
const name = '訂單系統';
複製代碼
// index.js
const name = '首頁';
require('./order.js');
console.log(`------\n${name}`);
複製代碼
模塊對外暴露的方式。對於要暴露的內容可以使用 module.exports
來導出,其導出內容格式是一個對象。也可以使用簡化形式 exports
es6
// module.exports.js
module.exports = {
name: '訂單系統',
total: (a, b) => {
return (a * b);
}
}
複製代碼
// exports.js
exports.name = '訂單系統';
exports.total = (a, b) => {
return (a * b);
}
複製代碼
上面兩種所要表達的功能是同樣的,內在邏輯是 exports
指向 module.exports
,module.exports
是初始化時建的一個空對象。因此千萬不要直接給 exports
賦值,還有 module.exports
和 exports
不要並存。上面第二個文件 exports.js
可這麼來理解:github
// 初始化(便於理解 exports 與 module.exports 的關係)
const module = {
exports: {}
};
const exports = module.exports;
// exports.js
exports.name = '訂單系統';
exports.total = (a, b) => {
return (a, b);
}
複製代碼
經過 require
導入。web
// 04/src/order.js
console.log('模塊文件 order.js');
exports.name = 'order name';
module.exports = {
name: '訂單系統',
total: (a, b) => {
return a * b;
}
};
exports.title = 'order title';
複製代碼
// 04/src/index.js
const title = require('./order.js').title;
const name = require('./order.js').name;
console.log(`exports.name 能展現麼?-------\n${name}`);
console.log(`exports.title 能展現麼?-------\n${title}`);
const total = require('./order.js').total;
setTimeout(function() {
console.log(`module.exports 能展現麼?${total(10, 10)}`);
}, 3000);
console.log('動態加載');
const modulesName = ['product.js', 'shopcar.js'];
modulesName.forEach(name => {
require(`./${name}`).name();
});
複製代碼
1.緩存加載,第二次導入文件時,無需加載,由於第一次導入已經加載過了,第二次直接使用上次導入的結果; 發現沒有?這是 order.js 文件
這個通知在控制檯裏面只打印了一次,而文件 order.js
實打實的引入了兩次。其原理是:咱們已經知道導出文件有 module
這個對象,咱們可能不知道的是這個對象有 loaded
這麼個屬性(記錄模塊是否被夾在過),其默認值是 false
,即沒有加載過。當該模塊第一次被加載後,loaded
值會變爲 true
,因此第二次引入該模塊就不會加載該模塊了。緩存
2.加載模塊支持動態加載;app
3.exports
和 module.exports
不要混合使用,不然 exports
會失效哦;函數
完整代碼可查看目錄 04 =>O(∩_∩)O~工具
ES6 Module 一樣是將每一個文件做爲一個模塊,模塊自身有其做用域。所不一樣的是,引入了關鍵字 import
(導入)和 exports
(導出),例子仍是前面的例子,語法糖發生了變化。post
1.默認導出,上面例子咱們都已接觸過了。不過每次只能導出一個,可直接導出對象、字符串、函數等。
// 導出字符串
export default '訂單系統';
複製代碼
// 導出對象,05/src/order.js
console.log('模塊文件 order.js');
export default {
name: '訂單系統',
total: (a, b) => {
return a * b;
}
};
複製代碼
2.命名導出,可以使用關鍵字 as
對導出變量重命名。
// 方式一,05/src/order1.js
export const name = '訂單系統1';
export const total = (a, b) => {
return a * b;
};
複製代碼
// 方式二,,05/src/order2.js
const name = '訂單系統2';
const total = (a, b) => {
return a * b;
};
export { name, total as getTotal };
複製代碼
使用關鍵字 import
導入,也可以使用關鍵字 as
對導入變量重命名,也可以使用 as
將導入變量的屬性添加到後面對象(order1
)中。
// 方式一
import { name, total as getProduct } from './order1';
複製代碼
// 方式二
import * as order2 from './order2';
複製代碼
// 05/src/index.js
import order from './order';
import { total as getProduct } from './order1';
import * as order2 from './order2';
console.log(`order.name: ${order.name}`);
console.log(`order1 getProduct: ${getProduct(10, 10)}`);
console.log(`order2 getTotal: ${order2.getTotal(10, 10)}`);
複製代碼
完整代碼可查看目錄 05 =>O(∩_∩)O~
二者本質區別在於:CommonJS 對模塊依賴是「動態」的,ES6 Module 是「靜態」的。
1.動態,模塊依賴關係是在代碼運行階段
require
路徑可動態指定;2.靜態,模塊依賴關係是在代碼編譯階段
咋一看,CommonJS 完美 KO ES6 Module 的方式。可事實並不是如此,ES6 Module 這種「靜態」方式有優點:
場景:導入一個模塊時,不一樣模塊模式是不同的。
// 06/src/commonJs.js
let csCount = 0;
module.exports = {
csCount,
csCountAdd: () => {
csCount += 10;
}
};
複製代碼
// 06/src/es6-module.js
let esCount = 0;
const esCountAdd = () => {
esCount += 10;
};
export { esCount, esCountAdd };
複製代碼
// 06/src/index.js
// CommonJS Module
let csCount = require('./commonJs').csCount;
let csCountAdd = require('./commonJs').csCountAdd;
console.log(`----commonjs 初次加載----\n${csCount}`);
csCountAdd();
console.log(`----commonjs 內部自加 10 -----\n${csCount}`);
csCount += 20;
console.log(`----commonjs 啓動項自加 20------\n${csCount}`);
// Es6 Module
import { esCount, esCountAdd } from './es6-module.js';
console.log(`----es6 初次加載----\n${esCount}`);
esCountAdd();
console.log(`----es6 內部自加 10 -----\n${esCount}`);
esCount += 20;
console.log(`----es6 啓動項自加 20------\n${esCount}`);
複製代碼
經過例子及上圖運行結果,可剖析
csCount()
,可是並無形成在文件 index.js
中副本(csCount
)的影響;而副本(csCount
)在文件 index.js
中可更改;esCount()
,文件 index.js
中副本(esCount
)的也隨之變化;而副本(esCount
)在文件 index.js
中是不可更改,便是隻讀的;完整代碼可查看目錄 06 =>O(∩_∩)O~
一般工程中是應該儘可能避免這種噁心的循環依賴的產生,由於會帶來複雜度。可儘管咱們知道這是很差的,也理性地避免發生。但鏈條長了,業務多了,人員多了,仍是不知不覺中「造孽般地」寫出這樣的代碼。
場景: A 依賴 B,B 依賴 C,C 依賴 D,D 依賴 A。其實 A 與 B 就互相依賴了。
// 07/src/a.js
const fileB = require('./b.js');
console.log(`----- a.js 文件展現 b.js 內容 ------\n`, fileB);
module.exports = '這是 a.js 文件';
複製代碼
// 07/src/b.js
const fileA = require('./a.js');
console.log(`----- b.js 文件展現 a.js 內容 ------\n`, fileA);
module.exports = '這是 b.js 文件';
複製代碼
// 07/src/index.js
require('./a.js');
複製代碼
咱們腦海中自運行結果是
----- b.js 文件展現 a.js 內容 ------
這是 a.js 文件
----- a.js 文件展現 b.js 內容 ------
這是 b.js 文件
複製代碼
可控制檯是
反覆檢查了代碼,沒錯啊,可本該顯示 這是 a.js 文件
,爲什麼展現 {}
?不行,我仍是要好好捋一捋
index.js
導入文件 a.js
,開始執行文件 a.js
,第一行導入文件 b.js
,此時進入文件 b.js
內部;b.js
第一行又導入文件 a.js
,循環依賴由此產生。這時執行權並無交回給文件 a.js
,而是直接取導出值,此刻文件 a.js
還未執行技結束,導出值就默認爲空對象,所以文件 b.js
執行打印語句時,顯示 ----- b.js 文件展現 a.js 內容 ------ {}
;b.js
執行結束了,執行權接着交回給文件 a.js
, 文件 a.js
繼續執行第二行,而後在控制檯打印 ----- a.js 文件展現 b.js 內容 ------ 這是 b.js 文件
。至此,這個流程結束;完整代碼可查看目錄 07 =>O(∩_∩)O~
// 08/src/a.js
import fileB from './b.js';
console.log(`----- a.js 文件展現 b.js 內容 ------\n`, fileB);
export default '這是 a.js 文件';
複製代碼
// 08/src/b.js
import fileA from './a.js';
console.log(`----- b.js 文件展現 a.js 內容 ------\n`, fileA);
export default '這是 b.js 文件';
複製代碼
// 07/src/index.js
import a from './a.js';
複製代碼
咱們腦海中自運行結果天然也是
----- b.js 文件展現 a.js 內容 ------
這是 a.js 文件
----- a.js 文件展現 b.js 內容 ------
這是 b.js 文件
複製代碼
可控制檯是
完整代碼可查看目錄 08 =>O(∩_∩)O~
我暈,仍是不對,此次不是空對象了,倒是 undefined
。那循環依賴該如何解決呢?
---> 等我下篇 <---