前端模塊化的理解(AMD,CMD,CommonJs,ES6)

前言

初期的web端交互還是很簡單,不需要太多的js就能實(shí)現(xiàn)。隨著時代的的發(fā)展,用戶對Web瀏覽器的性能也提出了越來越高的要求,瀏覽器也越來越多的承擔(dān)了更多的交互,不再是寥寥數(shù)語的js就能解決的,那么就造成了前端代碼的日益膨脹,js之間的相互依賴也會越來越多,此時就需要使用一定的規(guī)范來管理js之間的依賴。
本文主要是什么是模塊化,為什么需要模塊化以及現(xiàn)下流行的模塊化規(guī)范:AMD,CMD,CommonJs,ES6。

什么是模塊化

要想理解模塊化是什么可以先理解模塊是什么?
模塊:能夠獨(dú)立命名并且能夠獨(dú)立完成一定功能的集合。
因此在js中就可以理解為模塊就是能夠?qū)崿F(xiàn)特定功能獨(dú)立的一個個js文件。
模塊化:就可以簡單的理解為將原來繁重復(fù)雜的整個js文件按功能或者按模塊拆成一個個單獨(dú)的js文件,然后將每一個js文件中的某些方法拋出去,給別的js文件去引用和依賴。

為什么需要模塊化

一、模塊化的進(jìn)程

1、全局function模式:將不同的功能封裝為不同的函數(shù)
缺點(diǎn):污染全局命名空間,容易引起命名沖突,看不出模塊間的依賴
2、namespace模式:封裝為對象模式
作用:減少全局變量,解決命名沖突
缺點(diǎn):數(shù)據(jù)不安全(外部函數(shù)可以修改模塊內(nèi)的數(shù)據(jù)),看不出模塊之間的依賴

const module = {
    data:1,
  getData(){console.log(this.data)}
}
module.data = 2; //這樣會直接修改模塊內(nèi)部的數(shù)據(jù)

3、IIFE模式:匿名函數(shù)自調(diào)用(閉包)
作用:解決了數(shù)據(jù)安全,數(shù)據(jù)是私有的,外部只能調(diào)用暴露的信息
缺點(diǎn):需要綁定到一個全局變量上例如window向外暴露,這樣也會有命名沖突的問題
4、IIFE增強(qiáng)模式:引入依賴
作用:解決了模塊直接的依賴問題
缺點(diǎn):引入js的時候需要注意引入的順序,并且當(dāng)依賴很多的時候也會有弊端

// IIFE模式:匿名函數(shù)自調(diào)用(閉包)
(function(window){
    let data = '這是IIFE模式';
  getData(){
    console.log(data);
  }
  window.module = { getData }
})(window)
// IIFE增強(qiáng)模式
(function(window,$){
    let data = '這是IIFE模式';
  getData(){
    console.log(data);
    $('body').css('background', 'red')
  }
  window.module = { getData }
})(window,jQuery);
//index.html
//需要注意引入的順序
<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
  myModule.foo()
</script>
//當(dāng)<script>過多的時候的缺點(diǎn):
// 1. 請求過多
// 2. 依賴模糊:不清楚依賴直接是什么關(guān)系,很容易因?yàn)橐氲捻樞驅(qū)е鲁鲥e
// 3. 難以維護(hù)

從以上的發(fā)展歷程來看雖然模塊化還不是那么的完善,但是也不難能發(fā)現(xiàn)模塊化的優(yōu)點(diǎn):

二、模塊化的優(yōu)點(diǎn):

  • 避免命名沖突
  • 更好的分離模塊,按需加載
  • 高復(fù)用性
  • 高維護(hù)性

模塊的規(guī)范

AMD

AMD(Asynchronous Module Definition):異步模塊定義。采用異步方式加載模塊,模塊的加載不影響后續(xù)語句的執(zhí)行。所有依賴這個模塊的語句,都定義在一個回調(diào)函數(shù)中,等到加載完成之后,這個回調(diào)函數(shù)才會運(yùn)行。瀏覽器環(huán)境要從服務(wù)器端加載模塊,這時就必須采用非同步模式,因此瀏覽器端一般采用AMD規(guī)范。
RequireJs是AMD規(guī)范的最佳實(shí)踐,所以我們用AMD規(guī)范的時候要引入requirejs。

AMD的語法:

define用來定義模塊;
require用來加載模塊,通常AMD框架會以require方法作為入口,進(jìn)行依賴關(guān)系分析并依次有序地進(jìn)行加載。
AMD是依賴前置,就是說,在define或require中方法里傳入的模塊數(shù)組會在一開始就下載并執(zhí)行

//define定義模塊
//第一個參數(shù):依賴的模塊,沒有可以不寫
define(['module1','module2'],function(m1,m2){
  //如果引入了module,但是沒有使用,模塊的內(nèi)容也會執(zhí)行
    return 模塊;
})
//引入模塊,相當(dāng)于主函數(shù)的引用
require(['module1','module2'],function(m1,m2){
  //使用m1,m2
  //如果引入了module,但是沒有使用,模塊的內(nèi)容也會執(zhí)行
})
AMD具體使用流程
  1. 引入requirejs
    a. requirejs官網(wǎng)下載:requirejs
    b. github:requirejs
    c. 或者直接用require的js鏈接放在index.html中引用(只建議在學(xué)習(xí)時候用): https://requirejs.org/docs/release/2.3.6/minified/require.js
  2. 定義模塊
//module1.js
define(function(){
    const msg='module1';
  return {
    msg
  }
})
//module2.js
define(function(){
    const msg="module2";
  return {
    msg
  }
})
//module3.js
define(['module2'],function(m2) {
    const msg="module3";
    const msg2 = m2.msg
    return {
        msg,
        msg2
    };
});
//main.js,入口文件不需要module2,因此就不需要引入
require.config({
    paths:{
        jquery:'jquery' //用于引入第三方庫,此處填寫的是路徑,文件的路徑;當(dāng)然也可以在index.html用script標(biāo)簽引入
    }
})
require(['module1','module3','jquery'],function(m1,m3,$){
    console.log(m1,'module1');
    console.log(m3,'module3');
    console.log($,'第三方庫');
});
console.log('模塊加載'); //會優(yōu)先打印
  1. index.html的引入
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
  <!--data-main:文件入口-->
    <script data-main="main" src="https://requirejs.org/docs/release/2.3.6/minified/require.js"></script>
</body>
</html>
  1. 輸出結(jié)果


    image

CMD

CMD(Common Module Definition):通用模塊定義。用于瀏覽器端,是除AMD以外的另一種模塊組織規(guī)范。結(jié)合了AMD與CommonJs(后面會講到)的特點(diǎn)。也是異步加載模塊。
與AMD不同的是:AMD推崇的是依賴前置,而CMD是依賴就近,延遲執(zhí)行。
依賴前置&&依賴就近,延遲執(zhí)行

//依賴前置:AMD
require(['module1','module2'],function(m1,m2){
    //依賴的模塊首先加載,無論后續(xù)是否會用到
})
//依賴就近,延遲執(zhí)行
define(funciton(require){
    const module1 = require('./module1'); //用到的時候再申明,不需要就不用申明,也就不會加載進(jìn)來       
})
CMD具體使用步驟
  1. 引入sea.js
    a. 官網(wǎng):https://seajs.github.io/seajs/docs/#downloads
    b. github:https://github.com/seajs/seajs
  2. 模塊定義
//module1.js
define(function(require,exports) {
    const msg='這是模塊1';
    // const module2 = require('./module2');
    exports.msg = msg; // 注意這里是用的exports
    // exports.module2 = module2; // 如果是多個就得這么寫,所以如果暴露多個接口不建議用exports
});
//module2.js
define(function(require,exports,module) {
    const msg="這是模塊2";
    module.exports = { //這里用的是module.exports,跟exports作用一樣
        msg
    }
});
//module3.js
define(function(require,exports,module) {
    const msg="這是模塊3";
    const module2 = require('./module2');
    module.exports = { //這里用的是module.exports,跟exports作用一樣
        msg,
        module2
    }
});
//main.js
define(function(require, exports,module) {
    const module1 = require('./module1');
    const module3 = require('./module3');
    console.log(module1);
    console.log(module3);
});
console.log('模塊加載');
  1. index.html引入
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="sea.js"></script>
    <script>
        seajs.use('main');
    </script>
</body>
</html>
  1. 輸出結(jié)果
    image

CommonJS

Node 應(yīng)用由模塊組成,采用 CommonJS 模塊規(guī)范。每個文件就是一個模塊,有自己的作用域。在一個文件里面定義的變量、函數(shù)、類,都是私有的,對其他文件不可見。在服務(wù)器端,模塊的加載是運(yùn)行時同步加載的;在瀏覽器端,模塊需要提前編譯打包處理。
CommonJs有4個畢竟重要的變量:modulerequire、exports、global

CommonJs的特點(diǎn):
  1. 所有代碼都運(yùn)行在模塊作用域,不會污染全局作用域;如果想要多個模塊共享一個變量,需要給global添加屬性(不建議這么用)
//module.js
global.data = '共享變量'; //添加到global的屬性是多個文件可以共享的
let x = {
    a:5
}
let b=0;
const add = function(val){
    x.a+=x.a;
  b=++b;
}
module.exports.x=x; //只有對外暴露了變量,在外部引用的時候才能獲取到,否則x,b就是模塊內(nèi)部的私有變量
module.exports.add = add;
  1. 模塊可以多次加載,但是只有再第一次加載的時候才會執(zhí)行,后續(xù)的加載都是使用的緩存結(jié)果;如果想要再次加載需要清除緩存,或者對外暴露一個函數(shù),加載之后執(zhí)行暴露的函數(shù)
// test1.js
let msg="測試緩存";
console.log(msg);
exports.msg=msg;
// test2.js
const msg = require('./test1');
const msg2 = require('./test1');
// 上面的輸出結(jié)果是:測試緩存
// 會發(fā)現(xiàn)只打印了一次結(jié)果,以此可以證明只有第一次加載的時候才會執(zhí)行,第二次加載的時候使用的是緩存的結(jié)果
// 刪除指定模塊的緩存
delete require.cache[moduleName] // moduleName必須是絕對路徑
// 刪除所有模塊緩存
Object.keys(require.cache).forEach(function(key){
    delete require.cache[key]
})
  1. 模塊加載的順序是按照再代碼中書寫的順序加載的,同步加載模塊。
  2. 引入的值其實(shí)是輸出值的拷貝(淺拷貝)。也就是說一旦值輸出之后,模塊內(nèi)的改變就不會影響這個值的改變(引用類型除外)
// example.js
let x= {
    a:5
};
let b=0;
const add = function(val){
    x.a+=x.a;
    b=++b;
    // return x.a++;
}
module.exports.x = x;
module.exports.b=b;
module.exports.add = add;
// main.js
const example = require('./example');
const add = require('./example').add;
example.add();
console.log(example.x) // {a:10}
console.log(example.b) // b:0 
CommonJs的語法
//對外暴露接口
module.exports
exports
//引入模塊
require(模塊的路徑) //模塊的路徑有多種寫法,后續(xù)補(bǔ)充

module.exports&&exports
module.exports: 每個模塊內(nèi)部,module代表了當(dāng)前這個模塊,module是一個對象,對外暴露的就是exports這個屬性,加載某個模塊其實(shí)就相當(dāng)于加載的module.exports這個屬性
exports: 其實(shí)就是module.exports的引用。為了使用方便,node為每個模塊創(chuàng)建了一個exports變量,這個變量就指向了module.exports。因此以下兩種做法是錯誤的。

//錯誤一
exports='msg'; // 此時相當(dāng)于改變了exports的指向,失去了與module.exports的聯(lián)系,也就失去了對外暴露接口的能力
//錯誤二
exports.msg='msg';
module.exports = 'Hello world'; // 上面的msg是無法對外暴露的,因?yàn)閙odule.exports被重新賦值了;此時對外暴露的就是『Hello world』

ES6

ES modules(ESM)是 JavaScript 官方的標(biāo)準(zhǔn)化模塊系統(tǒng)。ES6模塊設(shè)計的思想是盡量的靜態(tài)化,使得編譯時就能知道模塊的依賴關(guān)系,以及輸入和輸出的變量。有兩個主要的命令:export和import。export用于對外暴露接口,import用于引入其他模塊。

ES6模塊的特點(diǎn):
  • 嚴(yán)格模式:ES6 的模塊自動采用嚴(yán)格模式
  • import read-only特性: import的屬性是只讀的,不能賦值,類似于const的特性
  • export/import提升: import/export必須位于模塊頂級,不能位于作用域內(nèi);其次對于模塊內(nèi)的import/export會提升到模塊頂部,這是在編譯階段完成的
  • 兼容在node環(huán)境下運(yùn)行
  • ES modules 輸出的是值的引用,輸出接口動態(tài)綁定,而 CommonJS 輸出的是值的拷貝
//ES6模塊值的引用 .mjs 主要是為了能用node環(huán)境運(yùn)行:node --experimental-modules
// example.mjs
let x= {
    a:5
};
let b=0;
const add = function(val){
    x.a+=x.a;
    b=++b;
}
export {x,b,add}
// main.mjs
import { x,b, add} from './example.mjs';
add();
console.log(x,b); // {a:10} 1 

export&&import用法
export:用于向外暴露接口
import:用于引入外部接口

// 方法一:
//export單個向外暴露接口
export const x = 1;
export const y = {a:1}
export const add = function(){console.log(123)}
//export一起向外暴露接口
const x=1;
const y={a:1};
const add = function(){console.log(123)};
export {x,y,add}
//import引入外部接口
//針對以上兩種方式import可以寫成如下兩種情況
import {x,y,add} from './exmaple'; //使用哪個就引入哪個
import * as moduleName from './exmaple'; // 全部引入,使用的時候使用moduleName.x
// 方法二:
//除了使用export 向外暴露接口外還可以使用export default向外暴露接口:同一個模塊中export可以有多個,但是export default只能有一個
const x=1;
const y={a:1};
export default { x,y}
// 對應(yīng)的import
import moduleName from './exmaple'; //全部引入,使用方式moduleName.x

總結(jié)

  • AMD:異步加載模塊,允許指定回調(diào)函數(shù)。AMD規(guī)范是依賴前置的。一般瀏覽器端會采用AMD規(guī)范。但是開發(fā)成本高,代碼閱讀和書寫比較困難。
  • CMD:異步加載模塊。CMD規(guī)范是依賴就近,延遲加載。一般也是用于瀏覽器端。
  • CommonJs:同步加載模塊,一般用于服務(wù)器端。對外暴露的接口是值的拷貝
  • ES6:實(shí)現(xiàn)簡單。對外暴露的接口是值的引用??梢杂糜跒g覽器端和服務(wù)端。

后記

模塊化的一次有一次的變更,讓系統(tǒng)模塊化變得也越來越好,但是響應(yīng)的也引起了一些問題。例如使用模塊的時候,發(fā)現(xiàn)有些模塊引入了但是并沒有真正的使用到,這樣就造成了代碼的冗余,多了一些不必要的代碼。這些模塊有時通過檢查是很難發(fā)現(xiàn)的,因此就要想如何能夠快速的去除這些無用的模塊呢,此時Tree Shaking就出現(xiàn)了;又比如如何能夠在開發(fā)代碼的時候比較便捷,然后在生產(chǎn)中又有高強(qiáng)度的兼容性呢,此時就出現(xiàn)了babel;又比如如何預(yù)處理模塊,此時出現(xiàn)了webpack……
出現(xiàn)了解決問題的辦法,就得繼續(xù)學(xué)習(xí)啦……

參考文章

前端模塊化的十年征程
徹底理清 AMD,CommonJS,CMD,UMD,ES6 modules
前端模塊化—CommonJS、CMD、AMD、UMD和ESM
前端模塊化詳解(完整版)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ~出來包括,嗯,每一項(xiàng)每一項(xiàng)工作,然後需要做什麼?在什麼節(jié)點(diǎn)做,啊,怎麼做?把這些全部都羅列出來,羅列出來之後我們...
    戈壁女神閱讀 567評論 0 0
  • 詩配畫是兒童畫的一種,只不過在兒童畫的基礎(chǔ)上配以古詩(現(xiàn)代詩)文字內(nèi)容,以這種獨(dú)特的方式來簡簡單單表現(xiàn)中國文...
    前郭225王冬芹閱讀 216評論 0 0
  • 就算是自己的日記本吧,畢竟還是太懶根本就不可能堅(jiān)持下來 ,天哪,好像就是這個樣子的。 繼續(xù)堅(jiān)持自己一貫的作風(fēng),起了...
    給一柄劍卍許一座城閱讀 280評論 0 1
  • 欣入梅嶺鑒清雪 移履不為春瓶折 貪賞失寒猶戀戀 省卻對窗嘆早謝 昆南于己亥端月十八
    昆南閱讀 412評論 0 5
  • 最近在使用Masonry出現(xiàn)了一個問題,以前居然沒有遇到過,也是神奇了,就是我在一個View上面添加一個label...
    coder_feng閱讀 398評論 0 0

友情鏈接更多精彩內(nèi)容