模塊化主要是幫助我們更好的組織代碼林束, 模塊允許我們將相關(guān)的變量和函數(shù)放在一個模塊中。 在ES6模塊化之前稽亏,JS語言并沒有模塊的概念壶冒,只有函數(shù)作用域和全局作用域非常容易發(fā)生命名沖突。 之前的RequireJS, SeaJS, AMD, UMD,CMD在一定層面上都是為了解決JS模塊化的問題截歉。ES6模塊取其精華:
- 它提供了簡潔的語法
- 以及異步的胖腾, 可配置的模塊加載
什么是模塊
模塊是自動運行在嚴格模式下并且沒有辦法退出運行的JavaScript代碼
- 在模塊的頂部this的值是undefined
2.其模塊不支持html風(fēng)格的代碼注釋 - 除非用default關(guān)鍵字,否則不能用這個語法導(dǎo)出匿名函數(shù)或類
任何未限制導(dǎo)出的變量怎披、函數(shù)或類都是模塊私有的胸嘁,無法從模塊外部訪問
為什么要使用模塊
目前最普遍的JS運行平臺便是瀏覽器,在瀏覽器中凉逛,所有的代碼都運行在同一個全局上下文中性宏, 這使得你即使更改應(yīng)用中的很小一部分, 你也要擔(dān)心可能會產(chǎn)生的命名沖突状飞。
傳統(tǒng)的JS應(yīng)用被分離在多個文件夾中毫胜,并且在構(gòu)建的時候連接在一起书斜,這稍顯笨重。所以人們開始將每個文件內(nèi)的代碼都包在一個自執(zhí)行函數(shù)中: (function(){ ... })();
酵使。 這種方法創(chuàng)建了一個本地作用域荐吉,于是最初的模塊化的概念產(chǎn)生了, 之后的CommonJS和AMD系統(tǒng)中所稱的模塊口渔, 也是由此實現(xiàn)的样屠。
創(chuàng)建模塊
一個JS模塊就是一個對其他模塊暴露一些內(nèi)部的屬性、方法的文件缺脉。 這里僅討論瀏覽器中的ES2016模塊系統(tǒng)痪欲。
每個模塊都有自己的上下文
和傳統(tǒng)的JS不同,在使用模塊時攻礼,你不必擔(dān)心污染全局作用域业踢。恰恰相反,你需要把所有你需要用到的東西從其他模塊中導(dǎo)入進來礁扮,這樣會使得模塊之間的依賴關(guān)系更為清晰
導(dǎo)入導(dǎo)出
可以使用ES6的新關(guān)鍵字 import
和 exports
來導(dǎo)入或?qū)С瞿K中的對象知举。 模塊可以導(dǎo)入和導(dǎo)出各種類型的變量,如函數(shù)太伊,對象雇锡,字符串,數(shù)字倦畅,布爾值等遮糖。
默認導(dǎo)出
每一個模塊都支持導(dǎo)出一個不具名的變量,這為默認導(dǎo)出:
// helloWord.js
export default function(){
console.log('111');
}
// main.js
import hello from './helloWord';
import anotherHello from './helloWord';
hello(); // 111
anotherHello(); //111
console.log(hello === anotherHello); //true
等價的CommonJS語法:
// helloWord.js
module.exports = function(){
console.log('111');
}
//main.js
var hello = require('./helloWord');
var anotherHello = require('./helloWord');
hello(); // 111
anotherHello(); //111
console.log(hello === anotherHello); //true
任何JS值都是可以被默認導(dǎo)出的:
// helloWord.js
export default 3.14
//export default { foo: 'bar' };
//export default 'hello word';
// main.js
import hello from './helloWord';
console.log(hello); // 3.14
這里如果把注釋放開叠赐,同樣輸出3.14欲账, 當(dāng)有多條export default語句,只會輸出第一條export default的值
// helloWord.js
export default 3.14
export default { foo: 'bar' };
export default 'hello word';
// main.js
import hello from './helloWord';
console.log(hello); // 3.14
具名導(dǎo)入
除了默認導(dǎo)出外芭概, ES6的模塊系統(tǒng)還支持導(dǎo)出任意數(shù)量個具名的變量:
const PI = 3.14
const value = 42;
export function hello(){
console.log('2111');
}
export {PI,value}
// 等同于CommonJS語法
// var PI = 3.14;
// var value = 42;
// module.exports.hello = function(){
// console.log('2111');
// }
// module.exports.PI = PI;
// module.exports.value = value;
導(dǎo)入的時候:
import {PI,value,hello} from './helloWord';
console.log(PI,value,hello());
導(dǎo)入的時候可以使用as
關(guān)鍵字來重命名導(dǎo)入的變量:
import {PI as PI2,value as val,hello as helloWord} from './helloWord';
console.log(PI2,val,helloWord());
結(jié)果是一樣的
導(dǎo)入所有
最簡單的赛不,在一條命令中導(dǎo)入一個模塊中所有變量的方法, 是使用*
標(biāo)記罢洲。 這樣一來踢故,被導(dǎo)入模塊中所有導(dǎo)出的變量都會變成它的屬性, 默認導(dǎo)出的變量則會被置于default
屬性中惹苗。
// helloWord.js
const PI = 3.14
const value = 42;
export function hello(){
console.log('2111');
}
export {PI,value}
// main.js
import * as Hello from './helloWord';
console.log(Hello);
注意一點
import * as foo from
和import foo from
的區(qū)別。 后者僅僅會導(dǎo)入默認導(dǎo)出的變量桩蓉,而前者則會在一個對象中導(dǎo)入所有,如:
// helloWord.js
const PI = 3.14
const value = 42;
const foo = {
'a':'123'
}
export function hello(){
console.log('2111');
}
export {PI,value,foo}
// main.js
import * as foo from './helloWord';
console.log(foo);
console.log(foo.foo);
對比四種模塊價值規(guī)范
- CommonJS
- AMD
- CMD
- ES6模塊
1. CommonJS
commonJS是服務(wù)器端的模塊化規(guī)范洽瞬, node.js 就是參照commonJS規(guī)范實現(xiàn)的本涕。commonJS中有一個全局的方法 require()用來加載模塊
function myModule(){
this.hello = function(){
return "hello"
}
this.goodbye = function(){
return "goodbye"
}
}
module.exports = myModule
其實module
變量代表當(dāng)前模塊
這樣就可以在其他模塊中使用這個模塊
var myModule = require('myModule');
var myModuleInstance = new myModule();
myModuleInstance.hello();
myModuleInstance.goodbye();
關(guān)于commonJS的更多,見CommonJS規(guī)范
2. AMD
commonJS定義模塊的方式和引入模塊的方式還是比較簡單的菩颖,但不適合瀏覽器端为障, 因為commonJS是同步加載的晦闰。 而AMD是異步加載的,模塊的加載不影響它后面語句的運行鳍怨。 所有依賴這個模塊的語句鹅髓,都定義在一個回調(diào)函數(shù)中京景,等到加載完成之后骗奖,這個回調(diào)函數(shù)才會運行。 這個用require.js實現(xiàn)AMD規(guī)范的模塊化执桌, 用require.config()指定引用路徑等,
通過define()
來定義模塊伴逸, 用requier加載模塊
首先我們需要引入require.js文件和一個入口文件main.js. main.js中配置require.config()并規(guī)定項目中用到的基礎(chǔ)模塊膘壶。
/** 網(wǎng)頁中引入require.js及main.js **/
<script src="js/require.js" data-main="js/main"></script>
/** main.js 入口文件/主模塊 **/
// 首先用config()指定各模塊路徑和引用名
require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min", //實際路徑為js/lib/jquery.min.js
"underscore": "underscore.min",
}
});
// 執(zhí)行基本操作
require(["jquery","underscore"],function($,_){
// some code here
});
引用模塊的時候,我們將模塊名放在[]
中作為 require()
的第一參數(shù)顷锰; 如果我們定義的模塊本身也依賴其他模塊亡问, 那就需要將他們放在[]
中作為define
的第一參數(shù)
// 定義math.js模塊
define(function () {
var basicNum = 0;
var add = function (x, y) {
return x + y;
};
return {
add: add,
basicNum :basicNum
};
});
// 定義一個依賴underscore.js的模塊
define(['underscore'],function(_){
var classify = function(list){
_.countBy(list,function(num){
return num > 30 ? 'old' : 'young';
})
};
return {
classify :classify
};
})
// 引用模塊,將模塊放在[]內(nèi)
require(['jquery', 'math'],function($, math){
var sum = math.add(10,20);
$("#sum").html(sum);
});
define
的第一個參數(shù)是依賴的模塊束世, 必須是一個數(shù)組床玻。 通過return來暴露接口
通過 require()
來加載模塊, 模塊的名字默認為模塊加載器請求的指定腳本的名字
require(['main'],function(main){
alert(main.foo());
})
require.js就是根據(jù)AMD規(guī)范來實現(xiàn)的薪丁, 優(yōu)點是:
- 實現(xiàn)js文件的異步加載, 避免網(wǎng)頁失去響應(yīng)
- 管理模塊之間的依賴性严嗜,便于代碼的編寫和維護
3. CMD
CMD也是異步模塊定義
CMD與AMD的區(qū)別:
CMD相當(dāng)于按需加載, 定義一個模塊的時候不需要立即制定依賴模塊茄蚯,在需要的時候require就可以了睦优,比較方便。
而AMD則相反汗盘,定義模塊的時候需要制定依賴模塊, 并以形參方式引入回調(diào)函數(shù)中隐孽。
// CMD 按需加載
define(function(require,exports,module){
var a = require('./helloWord');
a.hello();
var b = require('./counter');
console.log(b.foo);
// 2111
// aaa
})
// AMD 定義模塊的時候需要制定依賴
define(['./helloWord','./counter'],function(a,b){
a.hello();
console.log(b.foo);
});
ES6
ES6在語言規(guī)格的層面上菱阵,實現(xiàn)了模塊功能,而且實現(xiàn)得相當(dāng)簡單都办, 完全可以取代現(xiàn)有的CommonJS和AMD規(guī)范虑稼, 成為瀏覽器和服務(wù)器通用的模塊解決方案。
ES6模塊主要有兩個功能: export
和import
export
用于對外輸出本模塊(一個文件可以理解為一個模塊)變量的接口
import
用于在一個模塊中加載另一個含有exoport接口的模塊
參考:
https://segmentfault.com/a/1190000010058955
https://juejin.im/post/5aaa37c8f265da23945f365c
https://segmentfault.com/a/1190000004100661
http://es6.ruanyifeng.com/#docs/module
深入系列:
https://github.com/mqyqingfeng/Blog/issues/108
https://zhuanlan.zhihu.com/p/33843378?group_id=947910338939686912
面試題
題目一:ES6與commonJS模塊的差異
1. commonJS模塊輸出的是一個值的拷貝槽卫,ES6模塊輸出的是值的引用胰蝠。
- commonJS模塊一旦輸出一個值,模塊內(nèi)部的變化就影響不到這個值茸塞。
- ES6模塊如果使用import從一個模塊加載變量钾虐,那些變量不會被緩存,而是成為一個指向被加載模塊的引用效扫,原始值變了直砂,import加載的值也會跟著變浩习。需要開發(fā)者自己保證,真正取值的時候能夠取到值洽蛀。
**2. commonJS 模塊是運行時加載构哺, ES6模塊是編輯時輸出接口
運行時加載: commonJS模塊就是對象验辞,即在輸入時是加載整個模塊矾柜,生成一個對象耻卡,然后再從整個對象上讀取方法头岔,這種加載稱為”運行時加載“鼠证。 commonJS腳本代碼在require的時候靠抑,就會全部執(zhí)行。一旦出現(xiàn)某個模板被”循環(huán)加載“颂碧,就只能輸出已經(jīng)執(zhí)行的部分载城,還未執(zhí)行的部分不會輸出。
編譯時加載: ES6模塊不是對象诉瓦,而是通過export命令顯式指定輸出的代碼, import時指定加載某個輸出值固额,而不是加載整個模塊煞聪,這種加載稱為”編譯時加載“