前端模塊化——完全搞懂AMD、CMD、ESM和CommonJS

咱們知道,在NodeJS以前,因爲沒有過於複雜的開發場景,前端是不存在模塊化的,後端纔有模塊化。NodeJS誕生以後,它使用CommonJS的模塊化規範。今後,js模塊化開始快速發展。javascript

模塊化的開發方式能夠提供代碼複用率,方便進行代碼的管理。一般來講,一個文件就是一個模塊,有本身的做用域,只向外暴露特定的變量和函數。目前流行的js模塊化規範有CommonJS、AMD、CMD以及ES6的模塊系統。下面開始一一介紹:php

CommonJS

NodeJS是CommonJS規範的主要實踐者,它有四個重要的環境變量爲模塊化的實現提供支持:moduleexportsrequireglobal。實際使用時,用module.exports定義當前模塊對外輸出的接口(不推薦直接用exports),用require加載模塊。html

複製// 定義模塊math.js var basicNum = 0; function add(a, b) { return a + b; } module.exports = { //在這裏寫上須要向外暴露的函數、變量 add: add, basicNum: basicNum } /** 必須加./路徑,不加的話只會去node_modules文件找 **/ // 引用自定義的模塊時,參數包含路徑,可省略.js var math = require('./math'); math.add(2, 5); // 引用核心模塊時,不須要帶路徑 var http = require('http'); http.createService(...).listen(3000);

CommonJS用同步的方式加載模塊。在服務端,模塊文件都存放在本地磁盤,讀取很是快,因此這樣作不會有問題。可是在瀏覽器端,限於網絡緣由,更合理的方案是使用異步加載。前端

exportsmodule.export區別:java

exports:對於自己來說是一個變量(對象),它不是module的引用,它是{}的引用,它指向module.exports的{}模塊。只能使用.語法 向外暴露變量。node

module.exportsmodule是一個變量,指向一塊內存,exportsmodule中的一個屬性,存儲在內存中,而後exports屬性指向{}模塊。既可使用.語法,也可使用=直接賦值。jquery

AMD和require.js

AMD規範採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。全部依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成以後,這個回調函數纔會運行。這裏介紹用require.js實現AMD規範的模塊化:用require.config()指定引用路徑等,用definde()定義模塊,用require()加載模塊。webpack

首先咱們須要引入require.js文件和一個入口文件main.js。main.js中配置require.config()並規定項目中用到的基礎模塊。es6

複製/** 網頁中引入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", } }); // 執行基本操做 require(["jquery","underscore"],function($,_){ // some code here });

引用模塊的時候,咱們將模塊名放在[]中做爲reqiure()的第一參數;若是咱們定義的模塊自己也依賴其餘模塊,那就須要將它們放在[]中做爲define()的第一參數。web

複製// 定義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 }; }) // 引用模塊,將模塊放在[]內 require(['jquery', 'math'],function($, math){ var sum = math.add(10,20); $("#sum").html(sum); });

CMD和sea.js

AMD的實現者require.js在申明依賴的模塊時,會在第一時間加載並執行模塊內的代碼:

複製define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { // 等於在最前面聲明並初始化了要用到的全部模塊 if (false) { // 即使沒用到某個模塊 b,但 b 仍是提早執行了。**這就CMD要優化的地方** b.foo() } });

CMD是另外一種js模塊化方案,它與AMD很相似,不一樣點在於:AMD推崇依賴前置、提早執行,CMD推崇依賴就近、延遲執行。此規範實際上是在sea.js推廣過程當中產生的。

複製/** AMD寫法 **/ define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { // 等於在最前面聲明並初始化了要用到的全部模塊 a.doSomething(); if (false) { // 即使沒用到某個模塊 b,但 b 仍是提早執行了 b.doSomething() } }); /** CMD寫法 **/ define(function(require, exports, module) { var a = require('./a'); //在須要時申明 a.doSomething(); if (false) { var b = require('./b'); b.doSomething(); } }); /** sea.js **/ // 定義模塊 math.js define(function(require, exports, module) { var $ = require('jquery.js'); var add = function(a,b){ return a+b; } exports.add = add; }); // 加載模塊 seajs.use(['math.js'], function(math){ var sum = math.add(1+2); });

ES6 Module

ES6 在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,旨在成爲瀏覽器和服務器通用的模塊解決方案。其模塊功能主要由兩個命令構成:exportimportexport命令用於規定模塊的對外接口,import命令用於輸入其餘模塊提供的功能。

複製/** 定義模塊 math.js **/ var basicNum = 0; var add = function (a, b) { return a + b; }; export { basicNum, add }; /** 引用模塊 **/ import { basicNum, add } from './math'; function test(ele) { ele.textContent = add(99 + basicNum); }

如上例所示,使用import命令的時候,用戶須要知道所要加載的變量名或函數名。其實ES6還提供了export default命令,爲模塊指定默認輸出,對應的import語句不須要使用大括號。這也更趨近於ADM的引用寫法。

複製/** export default **/ //定義輸出 export default { basicNum, add }; //引入 import math from './math'; function test(ele) { ele.textContent = math.add(99 + math.basicNum); }

ES6的模塊不是對象,import命令會被 JavaScript 引擎靜態分析,在編譯時就引入模塊代碼,而不是在代碼運行時加載,因此沒法實現條件加載。也正由於這個,使得靜態分析成爲可能。

ES6 模塊的特徵:

  • 嚴格模式:ES6 的模塊自動採用嚴格模式
  • import read-only特性: import的屬性是隻讀的,不能賦值,相似於const的特性
  • export/import提高: import/export必須位於模塊頂級,不能位於做用域內;其次對於模塊內的import/export會提高到模塊頂部,這是在編譯階段完成的

ES6 模塊與 CommonJS 模塊的差別

1. CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用
  • CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值
  • ES6 模塊的運行機制與 CommonJS 不同。JS 引擎對腳本靜態分析的時候,遇到模塊加載命令import,就會生成一個只讀引用。等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊裏面去取值。換句話說,ES6 的import有點像 Unix 系統的「符號鏈接」,原始值變了,import加載的值也會跟着變。所以,ES6 模塊是動態引用,而且不會緩存值,模塊裏面的變量綁定其所在的模塊。
2. CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口
  • 運行時加載: CommonJS 模塊就是對象;即在輸入時是先加載整個模塊,生成一個對象,而後再從這個對象上面讀取方法,這種加載稱爲「運行時加載」。
  • 編譯時加載: ES6 模塊不是對象,而是經過 export 命令顯式指定輸出的代碼,import時採用靜態命令的形式。即在import時能夠指定加載某個輸出值,而不是加載整個模塊,這種加載稱爲「編譯時加載」。模塊內部引用的變化,會反應在外部。

CommonJS 加載的是一個對象(即module.exports屬性),該對象只有在腳本運行完纔會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成。

廢話很少說,直接看代碼:

首先看個CommonJS輸出拷貝的例子:

複製// a.js let a = 1; let b = { num: 1 } setTimeout(() => { a = 2; b = { num: 2 }; }, 200); module.exports = { a, b, }; // main.js // node main.js let {a, b} = require('./a'); console.log(a); // 1 console.log(b); // { num: 1 } setTimeout(() => { console.log(a); // 1 console.log(b); // { num: 1 } }, 500);

所謂輸出拷貝,若是瞭解過 NodeJS 或者 webpack 對 CommonJS 的實現(不瞭解能夠看這篇文章),就會知道:exports對象是模塊內外的惟一關聯, CommonJS 輸出的內容,就是exports對象的屬性,模塊運行結束,屬性就肯定了

再看ES6 Module輸出的例子:

複製// a.mjs let a = 1; let b = { num: 1 } setTimeout(() => { a = 2; b = { num: 2 }; }, 200); export { a, b, }; // main.mjs // node --experimental-modules main.mjs import {a, b} from './a'; console.log(a); // 1 console.log(b); // { num: 1 } setTimeout(() => { console.log(a); // 2 console.log(b); // { num: 2 } }, 500);

以上就是 ES6 Module 輸出引用和 CommonJS 輸出值的區別,模塊內部引用的變化,會反應在外部,這是 ES6 Module 的規範。

總結

  1. AMD/CMD/CommonJs 是js模塊化開發的規範,對應的實現是require.js/sea.js/Node.js

  2. CommonJs 主要針對服務端,AMD/CMD/ES Module主要針對瀏覽器端,容易混淆的是AMD/CMD。(順便提一下,針對服務器端和針對瀏覽器端有什麼本質的區別呢?服務器端通常採用同步加載文件,也就是說須要某個模塊,服務器端便停下來,等待它加載再執行。這裏若是有其餘後端語言,如java。而瀏覽器端要保證效率,須要採用異步加載,這就須要一個預處理,提早將所須要的模塊文件並行加載好。)

  3. AMD/CMD區別,雖然都是並行加載js文件,但仍是有所區別,AMD是預加載,在並行加載js文件同時,還會解析執行該模塊(由於還須要執行,因此在加載某個模塊前,這個模塊的依賴模塊須要先加載完成);而CMD是懶加載,雖然會一開始就並行加載js文件,可是不會執行,而是在須要的時候才執行。

  4. AMD/CMD的優缺點.一個的優勢就是另外一個的缺點, 能夠對照瀏覽。
    AMD優勢:加載快速,尤爲遇到多個大文件,由於並行解析,因此同一時間能夠解析多個文件。
    AMD缺點:並行加載,異步處理,加載順序不必定,可能會形成一些困擾,甚至爲程序埋下大坑。

    CMD優勢:由於只有在使用的時候纔會解析執行js文件,所以,每一個JS文件的執行順序在代碼中是有體現的,是可控的。

    CMD缺點:執行等待時間會疊加。由於每一個文件執行時是同步執行(串行執行),所以時間是全部文件解析執行時間之和,尤爲在文件較多較大時,這種缺點尤其明顯。(PS:從新看這篇文章,發現這裏寫的不是很準確。確切來講,JS是單線程,全部JS文件執行時間疊加在AMD和CMD中是同樣的。可是CMD是使用時執行,無法利用空閒時間,而AMD是文件加載好就執行,每每能夠利用一些空閒時間。這麼來看,CMD比AMD的優勢仍是很明顯的,畢竟AMD加載好的時候也未必就是JS引擎的空閒時間!)

  5. CommonJS 和 ES Module 區別:CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用

  6. 如何使用?CommonJs 的話,由於 NodeJS 就是它的實現,因此使用 node 就行,也不用引入其餘包。AMD則是經過<script>標籤引入require.js,CMD則是引入sea.js

文章來源 : https://www.cnblogs.com/chenwenhao/p/12153332.html

相關文章
相關標籤/搜索