最近在搞跨平臺(tái)解決方案盗蟆,討論關(guān)于模塊劃分的問(wèn)題以及如何盡量多的復(fù)用邏輯代碼镜撩。于是就有了此文章预柒,之前的博客也寫過(guò),不過(guò)由于主機(jī)商跑路袁梗,寶貴的資源也就沒(méi)了宜鸯,說(shuō)多了都是淚~ 這里按模塊化發(fā)展的歷史回溯的時(shí)間序
一. ES6 Moudle
這個(gè)是目前前端小伙伴接觸的最多的,是瀏覽器和服務(wù)端通用的模塊化解決方案遮怜,主要命令為:export
和import淋袖。export
用于導(dǎo)出本模塊對(duì)外的接口,import
用于導(dǎo)入某個(gè)模塊的功能锯梁。
//modulA.js
export var goingta = "goingta";
export function hello() { console.log("hello world"); }
const fn = function() { goingta = "new goingta"; console.log("fn"); };
export { fn };
const come = 1; export default come;
// export default const errorDefault = 2 // default 不能導(dǎo)出變量表達(dá)式
//index.js
import come, { fn, hello } from "./moduleA.js";
import * as aModule from "./moduleA.js";
fn();
hello();
console.log("come", come);
console.log("aModule", aModule); //aModule Module{default: 1, fn: ? (),…}
console.log("aModule.goingta", aModule.goingta); //new goingta
從上面例子即碗,我們可以看到export
可以在任意地方導(dǎo)出變量,方法陌凳,并且也可以將已有變量或方法包在一個(gè){}
對(duì)象里面導(dǎo)出剥懒,但是最終都會(huì)包在一個(gè){}
里面,簡(jiǎn)單理解就是:
1. 如果單獨(dú)導(dǎo)出一個(gè)變量或方法則是往將要導(dǎo)出{}對(duì)象里面添加屬性合敦。
2. 如果導(dǎo)出的是{}初橘,則和已生成的導(dǎo)出{}對(duì)象合并。
然后說(shuō)一下特例export default
充岛,這個(gè)是在導(dǎo)出對(duì)象里面加一個(gè)default
屬性保檐,還有一點(diǎn)值得注意的是export default
后面不能跟變量表達(dá)式。
最后還有一個(gè)重點(diǎn)是崔梗,ES6 Moudle是編譯時(shí)輸出夜只,并且是值引用。
二. CommonJS
CommonJS
最主要的代表就是Node.js
蒜魄,主要命令:module
扔亥、exports
爪膊、require
。其中有個(gè)令人疑惑的點(diǎn)是exports
和module.exports
砸王,其實(shí)理解起來(lái)也很簡(jiǎn)單,就是在模塊里面加了一句: exports = module.exports = {};
exports
和module.exports
指向同一個(gè)內(nèi)存區(qū)域峦阁,只要在exports
加了屬性谦铃,則module.exports
會(huì)跟著變化,但是最終導(dǎo)出對(duì)外的接口是以module.exports
為準(zhǔn)榔昔,所以不推薦直接使用exports
驹闰。例如:
//moduleA.js
let goingta = "我是goingta";
exports.goingta = goingta;
exports.fn = function() { goingta = "new goingta"; };
exports = "把exports指向其他區(qū)域";
module.exports = "我現(xiàn)在沒(méi)有g(shù)oingta了,也沒(méi)有fn了"; // 這行代碼注釋掉會(huì)有不一樣的結(jié)果
// index.js
let obj = require("./moduleA.js");
console.log(obj);
最終輸出的是:"我現(xiàn)在沒(méi)有g(shù)oingta了撒会,也沒(méi)有fn了"嘹朗,如果把最后一行代碼注釋掉則輸出:{goingta: "我是goingta", fn:? ()}
對(duì)于CommonJS
規(guī)范來(lái)說(shuō),很重要的一點(diǎn)是CommonJS
輸出的是一個(gè)值拷貝诵肛,并且是運(yùn)行時(shí)加載屹培。
把上面的示例代碼簡(jiǎn)化一下就可以看出:
//moduleA.js
let goingta = "我是goingta";
module.exports = { goingta };
module.exports.fn = function() { goingta = "new goingta"; };
//index.js
let obj = require("./moduleA.js");
obj.fn();
console.log("obj.goingta", obj.goingta);
最終結(jié)果輸出:"我是goingta"
三. ES6 Module和CommonJS的區(qū)別
從上面的結(jié)論我們不難得出,ES6 Module
和CommonJS
的本質(zhì)區(qū)別:
1. 引用方式:CommonJS模塊輸出是值的拷貝怔檩,ES6 Module模塊輸出的值是引用
2. 時(shí)機(jī):CommonJS是運(yùn)行時(shí)加載褪秀,ES6 Module是編譯是輸出
四. 模塊化歷史
差不多在6年前,當(dāng)項(xiàng)目越來(lái)越大薛训,用JS模擬劃分出來(lái)的類導(dǎo)致文件越來(lái)越多以及需要前置依賴的各種庫(kù)(那個(gè)時(shí)代的三叉戟jQuery
媒吗,Backbone
,Underscore
)乙埃,模塊化以及各種腳手架開(kāi)始興起闸英,于是有了AMD
和CMD
以及當(dāng)年這兩種規(guī)范之爭(zhēng)-AMD推崇依賴前置、提前執(zhí)行介袜,CMD
推崇依賴就近甫何、延遲執(zhí)行。AMD
的代表是require.js
米酬,CMD
的代表是玉伯的Sea.js
五. AMD和CMD
AMD
規(guī)范是采用異步方式沛豌,依賴前置必須一開(kāi)始就寫好,所有的依賴加載完成后才會(huì)執(zhí)行回調(diào)函數(shù)里的內(nèi)容:
// moduleB.js
//一開(kāi)始就要寫好這個(gè)模塊所有的依賴
define(["jQuery", "underscore", "moduleA"], function($, _) {
if (false) {
// 即便沒(méi)用到moduleA赃额,也會(huì)被提前執(zhí)行
moduleA.doSomething()
}
});
`CMD`規(guī)范按玉伯大大的原話`"是 Sea.JS 在推廣過(guò)程中對(duì)模塊定義的規(guī)范化產(chǎn)出"`加派,并且當(dāng)時(shí)在國(guó)內(nèi)認(rèn)可度很高。依賴就近書寫:
// moduleB.js
define(function(require, exports, module) {
var moduleA = require('moduleA.js');//用到時(shí)才寫
moduleA..doSomething()
//一堆其他業(yè)務(wù)代碼
var $ = require('jquery.js');
$.xxxx()
});
六. 更早以前
在07年左右正式有前端開(kāi)發(fā)這個(gè)職位后(以前都是后端研發(fā)兼寫前端代碼)跳芳,SEO芍锦、性能優(yōu)化、語(yǔ)義模板飞盆、瀏覽器兼容娄琉、代碼混淆加密等等都需要前端開(kāi)發(fā)承接是次乓,往往是一個(gè)公用的base.js
外加幾個(gè)業(yè)務(wù)js放在html
里面,然后用defer
和async
標(biāo)記:
<script src="base.js" async></script>
<script src="moduleA.js" defer></script>
defer
和async
的區(qū)別在于:defer
是“渲染完再執(zhí)行”孽水,async
是“下載完就執(zhí)行”票腰。