前端模塊化規範筆記

目前通行的Javascript的模板規範共有兩種:CommonJSAMD

commonjs

nodejs的模塊系統,是參照commonjs規範實現的javascript

commonjs即爲服務器端模塊的規範。 commonjs的規範: 根據commonjs規範,一個單獨的文件就是一個模塊。加載模塊使用require方法,該方法讀取一個文件並執行,最後返回文件內部的exports對象java

commonjs模塊的加載原理

commonjs模塊不管加載多少次,都只會在第一次加載時運行一次,之後再加載,就返回第一次運行的結果,除非手動清除系統緩存。node

AMD

commonjs規範加載模塊是同步的,也就是說,只有加載完成,才能執行後面的操做。AMD規範則是非同步加載模塊,容許指定回調函數。因爲Node.js主要用於服務器編程,模塊文件通常都已經存在於本地硬盤,因此加載起來比較快,不用考慮非同步加載的方式,因此commonjs規範比較適用。可是,若是是瀏覽器環境,要從服務器端加載模塊,這時就必須採用非同步模式,所以瀏覽器端通常採用AMD規範。es6

能夠理解爲AMD即爲能在客戶端環境,而且能兼容服務器端模塊的一種模塊規範web

  • AMD的模塊定義:
    AMD規範使用define方法定義模塊編程

    Define第一個參數表達依賴的模塊數組,第二個爲加載完依賴的模塊數組後,模塊執行的函數
  • AMD的模塊加載定義:跟commonjs同樣,AMD也採用require()語句來加載模塊,可是與commonjs不一樣的是,它要求有兩個參數:
    第一個參數[module],是一個數組,裏面的成員就是要加載的模塊;第二個參數callback,則是加載成功以後的回調函數數組

AMDCMD對比

  1. 對於依賴的模塊,AMD是提早執行,CMD是延遲執行。不過 RequireJS 從 2.0 開始,也改爲能夠延遲執行(根據寫法不一樣,處理方式不一樣)。CMD推崇 as lazy as possible.瀏覽器

  2. CMD推崇依賴就近,AMD推崇依賴前置緩存

  3. AMDAPI默認是一個當多個用,CMD的API 嚴格區分,推崇職責單一。好比AMD裏,require分全局require和局部require,都叫requireCMD裏,沒有全局 require,而是根據模塊系統的完備性,提供seajs.use來實現模塊系統的加載啓動。CMD裏,每一個API都簡單純粹。服務器

ES6 Modules

ES6模塊的設計思想,是儘可能的靜態化,使得編譯時就能肯定模塊的依賴關係,以及輸入和輸出的變量。commonjsAMD模塊,都只能在運行時肯定這些東西。好比,commonjs模塊就是對象,輸入時必須查找對象屬性。

// CommonJS模塊
let { stat, exists, readFile } = require('fs');

// 等同於
let _fs = require('fs');
let stat = _fs.stat, exists = _fs.exists, readfile = _fs.readfile;

上面代碼的實質是總體加載fs模塊(即加載fs的全部方法),生成一個對象(_fs),而後再從這個對象上面讀取3個方法。這種加載稱爲「運行時加載」,由於只有運行時才能獲得這個對象,致使徹底沒辦法在編譯時作「靜態優化」。

ES6模塊不是對象,而是經過export命令顯式指定輸出的代碼,輸入時也採用靜態命令的形式。

// ES6模塊
import { stat, exists, readFile } from 'fs';

上面代碼的實質是從fs模塊加載3個方法,其餘方法不加載。這種加載稱爲「編譯時加載」,即ES6能夠在編譯時就完成模塊加載,效率要比CommonJS模塊的加載方式高。固然,這也致使了無法引用ES6模塊自己,由於它不是對象。

因爲ES6模塊是編譯時加載,使得靜態分析成爲可能。有了它,就能進一步拓寬JavaScript的語法,好比引入宏(macro)和類型檢驗(type system)這些只能靠靜態分析實現的功能。

除了靜態加載帶來的各類好處,ES6模塊還有如下好處。

再也不須要UMD模塊格式了,未來服務器和瀏覽器都會支持ES6模塊格式。目前,經過各類工具庫,其實已經作到了這一點。
未來瀏覽器的新API就能用模塊格式提供,再也不必要作成全局變量或者navigator對象的屬性。
再也不須要對象做爲命名空間(好比Math對象),將來這些功能能夠經過模塊提供。
瀏覽器使用ES6模塊的語法以下。

<script type="module" src="foo.js"></script>
上面代碼在網頁中插入一個模塊foo.js,因爲type屬性設爲module,因此瀏覽器知道這是一個ES6模塊。

Node的默認模塊格式是CommonJS,目前還沒決定怎麼支持ES6模塊。因此,只能經過Babel這樣的轉碼器,在Node裏面使用ES6模塊。

用法

export
優先考慮這種寫法而不是一個一個的export

// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export {firstName, lastName, year};

提供對外接口必須在接口名與模塊內部變量之間保持一一對應關係

// 報錯
function f() {}
export f;

// 正確
export function f() {};

// 正確
function f() {}
export {f};

最後,export命令能夠出如今模塊的任何位置,只要處於模塊頂層就能夠。若是處於塊級做用域內,就會報錯,下一節的import命令也是如此。這是由於處於條件代碼塊之中,就無法作靜態優化了,違背了ES6模塊的設計初衷。

import
使用export命令定義了模塊的對外接口之後,其餘JS文件就能夠經過import命令加載這個模塊(文件)。

// main.js

import {firstName, lastName, year} from './profile';

function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}

上面代碼的import命令,就用於加載profile.js文件,並從中輸入變量。import命令接受一個對象(用大括號表示),裏面指定要從其餘模塊導入的變量名。大括號裏面的變量名,必須與被導入模塊(profile.js)對外接口的名稱相同。

若是想爲輸入的變量從新取一個名字,import命令要使用as關鍵字,將輸入的變量重命名。

import { lastName as surname } from './profile';

注意,import命令具備提高效果,會提高到整個模塊的頭部,首先執行。

ES6模塊加載的實質

ES6模塊加載的機制,與CommonJS模塊徹底不一樣。CommonJS模塊輸出的是一個值的拷貝,而ES6模塊輸出的是值的引用。
ES6的輸入有點像Unix系統的「符號鏈接」,原始值變了,import輸入的值也會跟着變。所以,ES6模塊是動態引用,而且不會緩存值,模塊裏面的變量綁定其所在的模塊。

參考資料

IMWEB團隊博客
知乎問答
阮一峯老師的ES6教程

相關文章
相關標籤/搜索