前言:本文著重介紹ES5和ES6的模塊化加載典蜕,至于AMD和CMD因?yàn)橐呀?jīng)過(guò)時(shí)了断盛,僅僅只是提了下。早些年的前端都僅僅只是靜態(tài)頁(yè)面愉舔,還沒有模塊化這個(gè)概念钢猛,但隨著時(shí)間的推移,前端代碼愈發(fā)龐大轩缤,那么自然而然會(huì)暴露很多問(wèn)題:
- 命名沖突
- 文件依賴
那個(gè)時(shí)候都是通過(guò)匿名自執(zhí)行函數(shù)來(lái)解決命名沖突命迈,文件依賴只能手動(dòng)保證引入的順序正確,直到后來(lái)某國(guó)外大神造出來(lái)Require.js火的,從此前端模塊化的概念就出現(xiàn)了壶愤;再后來(lái)淘寶前端大神玉伯根據(jù)require.js的思想造出了sas.js,至此馏鹤,兩大模塊加載器在前端領(lǐng)域獨(dú)領(lǐng)風(fēng)騷一段時(shí)間征椒。
1. AMD
AMDRequireJS 在推廣過(guò)程中對(duì)模塊定義的規(guī)范化產(chǎn)出,是瀏覽器端的模塊化解決方案湃累。
2. CMD
CMD是 SeaJS 在推廣過(guò)程中對(duì)模塊定義的規(guī)范化產(chǎn)出勃救,是瀏覽器端的模塊化解決方案。
3. AMD與CMD的差異
- AMD是提前執(zhí)行(RequireJS2.0開始支持延遲執(zhí)行治力,不過(guò)只是支持寫法蒙秒,實(shí)際上還是會(huì)提前執(zhí)行),CMD是延遲執(zhí)行
- AMD推薦依賴前置宵统,CMD推薦依賴就近
4. CommonJS
CommonJS是NodeJs服務(wù)器端模塊的規(guī)范晕讲,ES Module是在ES5中推出的,基于CommonJS規(guī)范马澈,而export和export default是ES6中的模塊化加載語(yǔ)法瓢省。
在這之前,必須了解 ES6 模塊與 CommonJS 模塊完全不同箭券。它們有兩個(gè)重大差異:
- CommonJS 模塊輸出的是一個(gè)值的拷貝,ES6 模塊輸出的是值的引用疑枯。
- CommonJS 模塊是運(yùn)行時(shí)加載辩块,ES6 模塊是編譯時(shí)輸出接口。
第二個(gè)差異是因?yàn)?CommonJS 加載的是一個(gè)對(duì)象(即module.exports屬性),該對(duì)象只有在腳本運(yùn)行完才會(huì)生成废亭。而 ES6 模塊不是對(duì)象国章,它的對(duì)外接口只是一種靜態(tài)定義,在代碼靜態(tài)解析階段就會(huì)生成豆村。具體可參考阮一峰大神的ES6文章Module加載語(yǔ)法液兽。
重點(diǎn)來(lái)了,下面著重介紹ES5中和ES6中的模塊化加載方案:
ES5中exports和module.exports的區(qū)別
module.exports才是真正的接口掌动,exports只不過(guò)是它的一個(gè)輔助工具四啰。nodejs只會(huì)導(dǎo)出module.exports的指向,最終返回給調(diào)用者的是module.exports而不是exports粗恢。
所有的exports收集到的屬性和方法柑晒,都賦值給了Module.exports。當(dāng)然眷射,這有個(gè)前提匙赞,就是module.exports本身不具備任何屬性和方法。
如果妖碉,module.exports已經(jīng)具備一些屬性和方法涌庭,那么exports收集來(lái)的信息將被忽略。
掛載在exports上的方式和屬性欧宜,都會(huì)傳遞給module.exports坐榆。二者沒區(qū)別。
exports.fun = function() {
console.log('Hello world')
}
exports.messgae = 'Nice to meet you'
var isEq = (exports === module.exports);
console.log(exports);
console.log(module.exports);
console.log(isEq);
// output:
// { fun: [Function], message: 'Nice to meet you' }
// { fun: [Function], message: 'Nice to meet you' }
// true
支持用import
和require
的具名和匿名引入
let mod = require('./mod.js') // mod: // { fun: [Function], message: 'Nice to meet you' }
import { fun, messgae } from './mod.js' // fun: [Function], message: Nice to meet you
直接把變量賦值給exports鱼鸠,那么exports指向變了猛拴,那就僅僅是exports不再指向module.exports
exports.fun = function() {
console.log('Hello world')
}
exports = 'Nice to meet you'
var isEq = (exports === module.exports);
console.log(exports);
console.log(module.exports);
console.log(isEq);
// output
// Nice to meet you
// { fun: [Function] }
// false
let mod = require('./mod.js') // mod: // { fun: [Function] }
直接把變量賦值給module.exports,那就說(shuō)明module.exports和exports的引用關(guān)系斷開了蚀狰,二者不相等了愉昆。
exports.fun = function() {
console.log('Hello world')
}
module.exports = 'Nice to meet you'
var isEq = (exports === module.exports);
console.log(exports);
console.log(module.exports);
console.log(isEq);
// output:
// { fun: [Function] }
// Nice to meet you
// false
let mod = require('./exports.js') // mod: Nice to meet you
??建議NodeJS開發(fā)者注意一下兩點(diǎn):
- 最好別分別定義module.exports和exports
- 導(dǎo)出對(duì)象用module.exports,導(dǎo)出多個(gè)方法和變量用exports
導(dǎo)出多個(gè)屬性或方法:
exports.fun = function() {
console.log('Hello world')
}
exports.message = 'Nice to meet you'
// 匿名引入
let mod = require('./mod.js') // { fun: [Function], message: 'Nice to meet you' }
// or 具名引入
let { fun, message } = require('./mod.js') // [Function], Nice to meet you
導(dǎo)出一個(gè)對(duì)象:
let fun = function() {
console.log('Hello world')
}
let message = 'Nice to meet you'
module.exports = {
fun,
message
}
// 匿名引入
let mod = require('./mod.js') // { fun: [Function], message: 'Nice to meet you' }
// or 具名引入
let { fun, message } = require('./mod.js') // [Function], Nice to meet you
??這里必須用module.exports,而不能用exports麻蹋,因?yàn)閚odejs只會(huì)導(dǎo)出module.exports的指向
ES6中export 和 export default區(qū)別
- export與export default均可用于導(dǎo)出常量跛溉、函數(shù)、文件扮授、模塊等
- 在一個(gè)文件或模塊中芳室,export、import可以有多個(gè)刹勃,export default僅有一個(gè)
- export方式只能具名導(dǎo)入堪侯,在導(dǎo)入時(shí)要加{ };export default則只能匿名導(dǎo)入
- export能直接導(dǎo)出變量表達(dá)式荔仁,export default不行伍宦。
export
const Programmer = {name: 'UncleFirefly',age:25}
export let message = 'hello'
export { Programmer }
console.log(module.exports) // { message: 'hello', Programmer: { name: 'UncleFirefly', age: 25 } }
import
引入時(shí)必須具名導(dǎo)入芽死,也就是需要聲明式指定對(duì)象里的key來(lái)導(dǎo)入你需要的
import { Programmer } from './mod.js' // Programmer: {name: 'UncleFirefly',age:25}
如果使用require
引入的話,則可以直接匿名引入
let mod = require('./mod.js') // mod: { message: 'hello', Programmer: { name: 'UncleFirefly', age: 25 } }
// or require也支持具名導(dǎo)入
let { Programmer } = require('./export.js') // Programmer: {name: 'UncleFirefly',age:25}
export default
const message = 'hello'
const Programmer = {name: 'UncleFirefly',age:25}
export default Programmer
// export default message // 報(bào)錯(cuò):export default只能用一次
console.log(module.exports) // { default: { name: 'UncleFirefly', age: 25 } }
只能匿名導(dǎo)入
import mod from './mod.js' // mod: { name: 'UncleFirefly', age: 25 }
如果使用require
引入的話次洼,必須通過(guò)default
屬性拿到實(shí)際導(dǎo)出的變量:
let mod = require('./mod.js') // mod: { default: { name: 'UncleFirefly', age: 25 } }
export與import混合使用
具名接口改為默認(rèn)接口的寫法如下:
export { es6 as default } from './someModule';
// 等同于
import { es6 } from './someModule';
export default es6;
同樣地关贵,默認(rèn)接口也可以改名為具名接口:
export { default as es6 } from './someModule';