web系列之模塊化——AMD、CMD、CommonJS、ES6 整理&&比較

AMD、CMD、CommonJS、ES6

1、AMD

AMD,Asynchronous Module Definition, 異步模塊定義。它是一個在瀏覽器端模塊化開發的規範。 它不是javascript原生支持,因此使用AMD規範進行頁面開發須要用到對應的庫,也就是RequireJS,AMD實際上是RequireJS在推廣的過程當中對模塊定義的範圍化的產出。javascript

requireJS主要解決兩個問題:html

  • 多個js文件存在依賴關係時,被依賴的文件須要早於依賴它的文件加載到瀏覽器
  • js加載的時候瀏覽器會阻塞渲染線程,加載文件越多,頁面失去響應的時間越長

用法: require須要一個root來做爲搜索依賴的開始(相似package.jsonmain),data-main來指定這個root前端

<script src="script/require.js" data-main="script/app.js"></script>
複製代碼

這樣就指定了rootapp.js,只有直接或者間接與app.js有依賴關係的module纔會被插入到html中。java

  • define()函數:用來定義模塊的函數。args0: 需引入模塊的名字數組,arg1:依賴引入以後的callbackcallback的參數就是引入的東西。若是有多個依賴,則參數按照引入的順序依次傳入。
define(['dependence_name'], (args) => {
// args就是從dependence_name引入的東西
// ... Your fucking code ...
   return your_export;
});
複製代碼
  • require()函數: 用來引入模塊的函數。
require(['import_module_name'], (args) => {
// args就是從import_module_name引入的東西
// ... Your fucking code ...
});
複製代碼
  • require.config配置:
    • baseUrl:加載module的根路徑
    • paths:用於映射不存在根路徑下面的模塊路徑
    • shimes:加載非AMD規範的js

2、CMD

CMD, Common Module Definition, 通用模塊定義。 CMD是在sea.js推廣的過程當中產生的。在CMD規範中,一個模塊就是一個文件。node

define(function(require, exprots, module) {
	const fs = require('fs');  //接受模塊標識做爲惟一參數
	// exports,module則和CommonJS相似
	exports.module = {
		props: 'value'
	};
});

seajs.use(['test.js'], function(test_exports) {
// ....
});
複製代碼
null AMD CMD
定義module時對依賴的處理 推崇依賴前置,在定義的時候就要聲明其依賴的模塊 推崇就近依賴,只有在用到這個module的時候纔去require
加載方式 async async
執行module的方式 加載module完成後就會執行該module,全部module都加載執行完成後會進入require的回調函數,執行主邏輯。依賴的執行順序和書寫的順序不必定一致,誰先下載完誰先執行,可是主邏輯 必定在全部的依賴加載完成後才執行(有點相似Promise.all)。 加載完某個依賴後並不執行,只是下載而已。在全部的module加載完成後進入主邏輯,遇到require語句的時候纔會執行對應的module。module的執行順序和書寫的順序是徹底一致的。

3、CommonJS

English time: Common -- 常識 W3C官方定義的API都只能基於Browser,而CommonJS則彌補了javascript這方面的不足。json

NodeJSCommonJS規範的主要實踐者。它有四個重要的環境變量爲模塊化的實現提供支持:module、exports、require、global。 實際用時,使用module.exports(不推薦使用exports)定義對外輸出的API,用require來引用模塊。CommonJS用同步的方式加載模塊。在Server上模塊文件都在本地磁盤,因此讀取很是快沒什麼不妥,可是在Browser因爲網絡的緣由,更合理的方案是異步加載。 CommonJS對模塊的定義主要分爲:模塊引用、模塊定義、模塊標識3個部分。數組

一、模塊引用:

const fs = require('fs');
複製代碼

require的執行步驟:瀏覽器

  1. 若是是核心模塊, 如fs,則直接返回模塊
  2. 若是是路徑,則拼接成一個絕對路徑,而後先讀取緩存require.cache再讀取文件。(若是沒有擴展名,則以js => json => node(以二進制插件模塊的方式去讀取)的順序去識別)
  3. 首次加載後的模塊會在require.cache中,因此屢次require,獲得的對象是同一個(引用的同一個對象)
  4. 在執行模塊代碼的時候,會將模塊包裝成如下模式,以便於做用域在模塊範圍以內。
(function (exports, require, module, __filename, __dirname) {
  // module codes
});
複製代碼
  1. 包裝以後的代碼同過vm原生模塊的runInThisContext()方法執行(相似eval,不過具備明確上下文不會污染環境),返回一個function對象。 最後將當前模塊對象的exportsrequire方法、module以及文件定位中獲得的完整文件路徑(包括文件名)和文件目錄傳遞給這個function執行。

二、模塊定義:

function fn() {}
exports.propName = fn;
module.exports = fn;
複製代碼

一個module對象表明模塊自己,exportsmodule的屬性。通常經過在exports上掛載屬性便可定義導出,也能夠直接給module.exports賦值來定義導出(推薦)。緩存

三、模塊標識:

模塊標識就是傳遞給require()方法的參數,能夠是相對路徑或者絕對路徑,也能夠是符合小駝峯命名的字符串。 NodeJSCommonJS的實現:Node中模塊分爲Node提供的核心模塊和用戶編寫的文件模塊網絡

核心模塊Node源代碼的編譯過程當中,編譯進了二進制執行文件。在Node啓動的時候部分核心模塊就加載到了memory中,因此在引用核心模塊的時候,文件定位和編譯執行步驟能夠省略,而且在路徑判斷中優先判斷,因此它的加載速度是最快的。 文件模塊則是在運行時動態加載,須要完整的路徑分析,文件定位、編譯執行等過程,速度較核心模塊慢。 在NodeJS中引入模塊須要經歷以下3個步驟:

  1. 路徑分析:module.paths = [‘當前目錄下的node_modules’, ‘父目錄下的node_modules’, …, ‘跟目錄下的node_modules’]

  2. 文件定位:文件擴展名分析、目錄和包的處理

    • 文件擴展名分析:Node會按.js => .json => .node的次序補足擴展名依次嘗試。(在嘗試的過程當中會調用同步的fs模塊來查看文件是否存在)
    • 目錄和包的處理:可能沒有對應的文件,可是存在相應的目錄。這時Node會在此目錄中查找package.json,並JSON.parsemain(入口文件)對應的文件。若是main屬性錯誤或者沒有package.json,則將index做爲main。若是沒有定位成功任何文件,則到下一個模塊路徑重複上述工做,若是整個module.paths都遍歷完都沒有找到目標文件,則跑出查找失敗錯誤。
  3. 編譯執行:在Node中每一個模塊文件都是一個對象,編譯執行是引入文件模塊的最後一個階段。定位到文件後,Node會新建一個模塊對象,而後根據路徑載入並編譯。對於不一樣的文件擴展名,其載入的方式也有所不一樣:

    • .js: 經過fs模塊同步讀取文件後編譯執行
    • .node:這是C++編寫的擴展文件,經過dlopen()加載最後編譯生成的文件。
    • .json:同.js文件,以後用JSON.parse解析返回結果。 其他文件: 都按js的方式解析。
null CommonJS ES6
keywords exports, require, module, __filename. __dirname import, export
導入 const path = require('fs'); 必須將一個模塊導出的全部屬性都引入 import path from 'path'; 能夠只引入某個
導出 module.exports = App; export default App;
導入的對象 隨意修改 值的copy 不能隨意修改 值的reference
導入次數 能夠任意次require,除了第一次,以後的require都是從require.cache中取得 在頭部導入,只能導入一次
加載 運行時加載 編譯時輸出接口

ES6模塊

ES6的模塊已經比較熟悉了,用法很少贅述,直接上碼:

import { prop } from 'app';   //從app中導入prop
import { prop as newProp } from 'app';   // 功能和上面同樣,不過是將導入的prop重命名爲newProp

import App from 'App';   // 導入App的default
import * as App from 'App';  // 導入App的全部屬性到App對象中

export const variable = 'value'; // 導出一個名爲variable的常量
export {variable as newVar};   // 和import 的重命名相似,將variable做爲newVar導出

export default variable = 'value';  // 將variable做爲默認導出
export {variable as default};   // 和上面的寫法基本同樣

export {variable} from 'module';  // 導出module的variable ,該模塊中沒法訪問
export {variable as newVar} from 'module';  // 下面的本身看 不解釋了
export {variable as newVar} from 'module';
export * from 'module';
複製代碼

ps:ES6模塊導入的變量(其實應該叫常量更準確)具備如下特色: 變量提高、至關於被Object.freeze()包裝過同樣、import/export只能在頂級做用域

ES6模塊區別於CommonJS的運行時加載,import 命令會被JavaScript引擎靜態分析,優先於模塊內的其餘內容執行(相似於函數聲明優先於其餘語句那樣), 也就是說在文件的任何位置import引入模塊都會被提早到文件頂部。

ES6的模塊 自動開啓嚴格模式,即便沒有寫'use strict'; 。 運行一個包含import聲明的模塊時,被引入的模塊先導入並加載,而後根據依賴關係,每一個模塊的內容會使用深度優先的原則進行遍歷。跳過已經執行過的模塊,避免依賴循環。

okey~接下來老哥再看看(查查)import到底幹啥了: 標準幾乎沒有談到import該作什麼,ES6將模塊的加載細節徹底交給了實現。 大體來講,js引擎運行一個模塊的時候,其行爲大體可概括爲如下四步:

  1. 解析:engine去解析模塊的代碼,檢查語法等。
  2. 加載:遞歸加載全部被引入的模塊,深度優先
  3. 連接:爲每一個新加載的模塊建立一個做用域,並將模塊中的聲明綁入其中(包括從其餘模塊中引入的)。 當js引擎開始執行加載進來的模塊中的代碼的時候,import的處理過程已經完了,因此js引擎執行到一行import聲明的時候什麼也不會幹。引入都是靜態實現的,等到代碼執行的時候就啥都不幹了。

既然說到了模塊(module),那就順便提一下它和腳本(script)的區別(注意,我這裏說的區別僅限於在Web瀏覽器中):

- module script
使用方式 (固然還有其餘的執行方式,在這裏不作過多討論) <script src="./source.js type="module" /> <script src="./source.js type="text/javascript" />
下載 ①遇到<script>時,會自動應用defer。
②下載 && 解析module。
③遞歸下載module中導入的資源。下載階段完成。
遇到<script>時默認阻塞文檔渲染,開啓下載。
執行方式 ①下載完成後會遞歸執行module中導入的資源。
②而後執行module自己。
ps:內聯module少了下載module自己的步驟,其餘步驟和引入的module相同。
默認是下載完成當即執行

參考連接:

前端模塊化:CommonJS,AMD,CMD,ES6

ES6 的模塊系統

深刻淺出NodeJS

相關文章
相關標籤/搜索