在瞭解es6Module與RequireJS以前,仍是須要先來簡單地瞭解下什麼是模塊化,模塊化開發node
模塊化是指在解決某一個複雜問題或者一系列的雜糅問題時,依照一種分類的思惟把問題進行系統性的分解以之處理。模塊化是一種處理複雜系統分解爲代碼結構更合理,可維護性更高的可管理的模塊的方式。es6
做爲一個模塊化系統所必須的能力:瀏覽器
歷史上,JavaScript 一直沒有模塊(module)體系,沒法將一個大程序拆分紅互相依賴的小文件,再用簡單的方法拼裝起來。其餘語言都有這項功能,好比 Ruby 的require、Python 的import,甚至就連 CSS 都有@import,可是 JavaScript 任何這方面的支持都沒有,這對開發大型的、複雜的項目造成了巨大障礙。緩存
因此社區制定了CommonJs規範,Node 從 Commonjs 規範中借鑑了思想因而有了 Node 的 module,而 AMD 異步模塊 也一樣脫胎於 Commonjs 規範,以後有了運行在瀏覽器上的 require.js。服務器
ES6 在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,徹底能夠取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案。異步
Node.js是commonJS規範的主要實踐者,它有四個重要的環境變量爲模塊化的實現提供支持:module、exports、require、global。實際使用時,用module.exports定義當前模塊對外輸出的接口,用require加載模塊。模塊化
// 定義模塊 area.js function area(radius) { return Math.PI * radius * radius; } // 在這裏寫上須要向外暴露的函數、變量 module.exports = { area: area } // 引用自定義的模塊時,參數包含路徑 var math = require('./math'); math.area(2);
可是咱們並無直接定義 module、exports、require這些模塊,以及 Node 的 API 文檔中提到的__filename、__dirname。那麼是從何而來呢?其實在編譯的過程當中,Node 對咱們定義的 JS 模塊進行了一次基礎的包裝:函數
(function(exports, require, modules, __filename, __dirname)) { ... })
這樣咱們即可以訪問這些傳入的arguments以及隔離了彼此的做用域。CommonJS 的一個模塊,就是一個腳本文件。require命令第一次加載該腳本,就會執行整個腳本,而後在內存生成一個對象。工具
{ id: '...', exports: { ... }, loaded: true, ... }
之後須要用到這個模塊的時候,就會到exports屬性上面取值。即便再次執行require命令,也不會再次執行該模塊,而是到緩存之中取值。commonJS用同步的方式加載模塊,只有在代碼執行到require的時候,纔回去執行加載。ui
CommonJS規範規定,每一個模塊內部,module變量表明當前模塊。這個變量是一個對象,它的exports屬性(即module.exports)是對外的接口。加載某個模塊,實際上是加載該模塊的module.exports屬性。
下面是Module._load的源碼
Module._load = function(request, parent, isMain) { // 計算絕對路徑 var filename = Module._resolveFilename(request, parent); // 第一步:若是有緩存,取出緩存 var cachedModule = Module._cache[filename]; if (cachedModule) { return cachedModule.exports; // 第二步:是否爲內置模塊 if (NativeModule.exists(filename)) { return NativeModule.require(filename); } // 第三步:生成模塊實例,存入緩存 var module = new Module(filename, parent); Module._cache[filename] = module; // 第四步:加載模塊 try { module.load(filename); hadException = false; } finally { if (hadException) { delete Module._cache[filename]; } } // 第五步:輸出模塊的exports屬性 return module.exports; };
// exportDemo.js count = 1; module.exports.count = count; module.exports.Hello = function() { var name; this.setName = function(newName) { name = newName; } this.sayHello = function() { console.log("hello Mr." + name); } this.getId = function() { return count++ } } var { Hello, count } = require('./exportDemo') var hello = new Hello(); // 讓count自增 console.log(hello.getId()); console.log(hello.getId()); // 發現獲取的count仍是原值 console.log(count) // 真正的count實際上是已經改了的 var newHello = new Hello(); console.log(newHello.getId()) var { Hello: newHello, count: newCount } = require('./exportDemo') console.log(newCount, 'newCount'); // 再次require,取得的newHello和以前require的Hello指向同一個拷貝 console.log(newHello === Hello)
es6在語言標準的層面上,實現了模塊功能。
ES6 模塊不是對象,而是經過export命令顯式指定輸出的代碼,再經過import命令輸入。
import { stat, exists, readFile } from 'fs';
上面代碼的實質是從fs模塊加載 3 個方法,其餘方法不加載。這種加載稱爲「編譯時加載」或者靜態加載,即 ES6 能夠在編譯時就完成模塊加載,效率要比 CommonJS 模塊的加載方式高。固然,這也致使了無法引用 ES6 模塊自己,由於它不是對象。
因爲 ES6 模塊是編譯時加載,使得靜態分析成爲可能。有了它,就能進一步拓寬 JavaScript 的語法,好比引入宏(macro)和類型檢驗(type system)這些只能靠靜態分析實現的功能。
除了靜態加載帶來的各類好處,ES6 模塊還有如下好處。
// exportDemo.mjs export let counter = 1; export function incCounter() { counter ++; } // importDemo.mjs import { counter, incCounter } from './exportDemo.mjs' incCounter(); console.log(counter) // 打印結果爲2,而不是初始值的1
這是一個從靜態到動態導入轉換的例子
// STATIC import './a.js'; import b from './b.js'; b(); import {c} from './c.js'; c(); // DYNAMIC import('./a.js').then(()=>{ console.log('a.js is loaded dynamically'); }); import('./b.js').then((module)=>{ const b = module.default; b('isDynamic'); }); import('./c.js').then(({c})=>{ c('isDynamic'); });
動態import()給咱們提供了用異步方式使用ES模塊的額外功能。可讓咱們根據咱們的須要動態或有條件地加載它們。
CommonJS 加載的是一個對象(即module.exports屬性),該對象只有在腳本運行完纔會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成。
如下這些頂層變量在ES6模塊之中都是不存在的