AMD,Asynchronous Module Definition, 異步模塊定義
。它是一個在瀏覽器端模塊化開發的規範。 它不是javascript
原生支持,因此使用AMD規範進行頁面開發須要用到對應的庫,也就是RequireJS
,AMD實際上是RequireJS
在推廣的過程當中對模塊定義的範圍化的產出。javascript
requireJS
主要解決兩個問題:html
js
文件存在依賴關係時,被依賴的文件須要早於依賴它的文件加載到瀏覽器js
加載的時候瀏覽器會阻塞渲染線程,加載文件越多,頁面失去響應的時間越長用法: require
須要一個root
來做爲搜索依賴的開始(相似package.json
的main
),data-main
來指定這個root
。前端
<script src="script/require.js" data-main="script/app.js"></script>
複製代碼
這樣就指定了root
是app.js
,只有直接或者間接與app.js
有依賴關係的module
纔會被插入到html
中。java
define()
函數:用來定義模塊的函數。args0
: 需引入模塊的名字數組,arg1
:依賴引入以後的callback
,callback
的參數就是引入的東西。若是有多個依賴,則參數按照引入的順序依次傳入。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
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的執行順序和書寫的順序是徹底一致的。 |
English time: Common -- 常識 W3C官方定義的API都只能基於Browser,而CommonJS則彌補了javascript這方面的不足。json
NodeJS
是CommonJS
規範的主要實踐者。它有四個重要的環境變量爲模塊化的實現提供支持:module、exports、require、global
。 實際用時,使用module.exports
(不推薦使用exports)定義對外輸出的API,用require
來引用模塊。CommonJS
用同步的方式加載模塊。在Server
上模塊文件都在本地磁盤,因此讀取很是快沒什麼不妥,可是在Browser
因爲網絡的緣由,更合理的方案是異步加載。 CommonJS
對模塊的定義主要分爲:模塊引用、模塊定義、模塊標識3個部分。數組
const fs = require('fs');
複製代碼
require的執行步驟:瀏覽器
js => json => node
(以二進制插件模塊的方式去讀取)的順序去識別)require.cache
中,因此屢次require,獲得的對象是同一個(引用的同一個對象)(function (exports, require, module, __filename, __dirname) {
// module codes
});
複製代碼
exports
、require
方法、module
以及文件定位中獲得的完整文件路徑
(包括文件名)和文件目錄
傳遞給這個function執行。function fn() {}
exports.propName = fn;
module.exports = fn;
複製代碼
一個module
對象表明模塊自己,exports
是module
的屬性。通常經過在exports
上掛載屬性便可定義導出,也能夠直接給module.exports
賦值來定義導出(推薦)。緩存
模塊標識就是傳遞給require()
方法的參數,能夠是相對路徑或者絕對路徑,也能夠是符合小駝峯命名的字符串。 NodeJS
中CommonJS
的實現:Node
中模塊分爲Node提供的核心模塊
和用戶編寫的文件模塊
。網絡
核心模塊在Node
源代碼的編譯過程當中,編譯進了二進制執行文件。在Node
啓動的時候部分核心模塊就加載到了memory
中,因此在引用核心模塊的時候,文件定位和編譯執行步驟能夠省略,而且在路徑判斷中優先判斷,因此它的加載速度是最快的。 文件模塊則是在運行時動態加載,須要完整的路徑分析,文件定位、編譯執行等過程,速度較核心模塊慢。 在NodeJS
中引入模塊須要經歷以下3個步驟:
路徑分析:module.paths = [‘當前目錄下的node_modules’, ‘父目錄下的node_modules’, …, ‘跟目錄下的node_modules’]
文件定位:文件擴展名分析、目錄和包的處理。
Node
會按.js => .json => .node
的次序補足擴展名依次嘗試。(在嘗試的過程當中會調用同步的fs模塊來查看文件是否存在)Node
會在此目錄中查找package.json
,並JSON.parse
出main
(入口文件)對應的文件。若是main
屬性錯誤或者沒有package.json
,則將index
做爲main
。若是沒有定位成功任何文件,則到下一個模塊路徑重複上述工做,若是整個module.paths
都遍歷完都沒有找到目標文件,則跑出查找失敗錯誤。編譯執行:在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的模塊已經比較熟悉了,用法很少贅述,直接上碼:
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
引擎運行一個模塊的時候,其行爲大體可概括爲如下四步:
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相同。 |
默認是下載完成當即執行 |