JavaScript 主流模塊(module)規範介紹

主流模塊規範

目前主流模塊規範有:javascript

規範名稱 運行環境 實現 加載方式
AMD(異步模塊定義) 客戶端 require.js 異步
CMD(通用模塊定義) 客戶端 sea.js 異步
CommonJS 服務端 NodeJS 同步(動態加載)
es6 客戶端 es6 靜態加載

AMD和CMD(ES5)

AMD 和 CMD 加載多個文件時都是異步加載html

區別:java

  1. AMD推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊
  2. CMD推崇就近依賴,只有在用到某個模塊的時候再去require

AMD

required.js 是 AMD 的實現node

使用:define(id?, dependencies?, factory);;jquery

// 定義模塊 myModule.js
define(['dependency'], function(){
    var name = 'Byron';
    function printName(){
        console.log(name);
    }

    return {
        printName: printName
    };
});

// 加載模塊
require(['myModule'], function (my){
  my.printName();
});

CMD

sea.js 是 CMD 的實現git

CMD 中一個模塊就是一個文件es6

使用:define(id?, dependencies?, factory);;github

// 定義模塊  myModule.js
define(function(require, exports, module) {
  var $ = require('jquery.js')
  $('div').addClass('active');
});

// 加載模塊
seajs.use(['myModule.js'], function(my){

});

CommonJS(ES5)

CommonJS 模塊就是對象,輸入時必須查找對象屬性。express

  • CommonJS 的加載稱爲「運行時加載」或者動態加載
  • 一個文件就是一個模塊,每一個模塊都是單獨的做用域

特色:api

  • 全部代碼都運行在模塊做用域,不會污染全局做用域。
  • 模塊能夠屢次加載,可是隻在第一次加載時運行一次,而後運行結果就被緩存了,之後再加載,就直接讀取緩存結果。要想讓模塊再次運行,必須清除緩存。
  • 模塊加載的順序,按照其在代碼中出現的順序。

module.exports

每一個模塊內部,都有一個module對象,表明當前模塊。它有如下屬性。

  • module.id 模塊的識別符,一般是帶有絕對路徑的模塊文件名。
  • module.filename 模塊的文件名,帶有絕對路徑。
  • module.loaded 返回一個布爾值,表示模塊是否已經完成加載。
  • module.parent 返回一個對象,表示調用該模塊的模塊。
  • module.children 返回一個數組,表示該模塊要用到的其餘模塊。
  • module.exports 表示模塊對外輸出的值
module.exports = 123

exports

爲了方便,Node爲每一個模塊提供一個 exports 變量,指向 module.exports

注意:

  • exports 變量直接指向一個單一值。單一值只能使用 module.exports 輸出
// 至關於每一個模塊頂部都有這個聲明
var exports = module.exports;

// 能夠給導出的對象添加屬性
exports.area = function (r) {
  return Math.PI * r * r;
};

// 不能夠導出一個單一值
exports = 123; // 無效

require

require 命令用於加載模塊文件,返回該模塊的 exports 對象

commonJS規範

Module(ES6)

ES6 模塊不是對象,而是經過 export 命令顯式指定輸出的代碼,再經過 import 命令輸入。

  • ES6 的模塊是「編譯時加載」或者靜態加載
  • ES6 使用基於文件的模塊,也就是說一個文件一個模塊
  • ES6 模塊是單例。也就是說,模塊只有一個實例,其中維護了它的狀態。每次向其餘模塊導入這個模塊的時候,獲得的是對單箇中心實例的引用。
  • ES6 模塊的API是靜態的。導出後的API是隻讀狀態
  • 模塊的公開 API 中暴露的屬性和方法並不只僅是普通的值或引用的賦值。它們是到內部模塊定義中的標識符的實際綁定(幾乎相似於指針)。
  • ES6 的模塊自動採用嚴格模式

優勢:

  1. 編譯時加載方法,效率高
  2. 再也不須要對象做爲命名空間了

缺點:

  1. es6模塊不是對象,因此沒法引用模塊自己

export

經過 export 命令規定模塊的對外接口。有兩種模式:

  • 命名導出(能夠多個):export **
  • 默認導出(只有一個):export default
// 導出單個
export let name1 = 1;

// 導出多個
export { name1, name2, ...};

// 重命名導出
export {
  name1 as master
}

// 解構導出並重命名
export const { name1, name2: bar } = o;

// 默認導出
export default expression;
export { name1 as default, … };

// 聚合模塊 - 輸入後立馬輸出
export * from …; // does not set the default export
export * as name1 from …;
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
export { default } from …;

import

經過 import 命令加載模塊

  • import 是靜態的,在編譯時就加載
  • import 命令會提高到頂部

使用:

  • 可導入整個模塊內容:使用 * 指定一個對象,全部輸出值都加載在這個對象上
  • 可導入單個接口:使用大括號包裹需引入值
  • 可導入多個接口:使用大括號包裹需引入值,多個值用逗號分隔
  • 可導入有別名的接口:使用 as 能夠設置別名
  • 執行加載模塊:直接 import 某個模塊,會執行,但不會輸入任何值
  • 導入默認值:不須要大括號,直接指定任意值。對應模塊內的 export default
// 執行 lodash 模塊,但不輸入任何值
import 'lodash';

// 非默認導出需有大括號。可使用 as 設置別名
import { firstName as fn, persion } from './profile.js';

// export default 默認導出不須要括號
import class from './class.js';

// 變量不容許改寫
fn = 123; // Syntax Error : 'firstName' is read-only;
// 對象屬性能夠改寫 - 不建議
persion.age = 18;

動態import

import() 能夠實現動態加載

  • import() 能夠像調用函數同樣動態導入模塊,並返回一個promise
  • import() 支持 await 關鍵字
  • import() 能夠在commonJS 模塊中運行

使用場景:

  • 不須要立刻使用的模塊(可用於異步加載,提升性能)
  • 須要根據場景判斷才引入的模塊
import('/modules/my-module.js')
.then((module) => {
  // Do something with the module.
});

async getModule() {
  if (true) {
    let module = await import('/modules/my-module.js');
  }
}

循環引用/循環加載

"循環加載"(circular dependency)指的是,a腳本的執行依賴b腳本,而b腳本的執行又依賴a腳本。

// a.js
var b = require('b');

// b.js
var a = require('a');

一般,"循環加載"表示存在強耦合,若是處理很差,還可能致使遞歸加載,使得程序沒法執行。

如何解決?commonJS 和 Es6 的module 給出了不一樣的解決方案。

commonJS

commonJS 是動態加載,執行一次就會緩存結果。若是出現某個模塊被"循環加載",返回的是當前已經執行的部分的值,而不是代碼所有執行後的值。因此,輸入變量時須要很是當心。

a.js

exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 執行完畢');

b.js

exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 執行完畢');

main.js

var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);

執行main.js

node main.js

在 b.js 之中,a.done = false
b.js 執行完畢
在 a.js 之中,b.done = true
a.js 執行完畢
在 main.js 之中, a.done=true, b.done=true

ES6 模塊

ES6 是靜態加載,不會緩存結果。 它只是生成一個指向被加載模塊的引用,每次都會根據引用動態去加載模塊取值。

// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar);
export let foo = 'foo';

// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo);
export let bar = 'bar';

執行

node --experimental-modules a.mjs
b.mjs
ReferenceError: foo is not defined -> 緣由: foo未定義。

解決:將foo寫成函數,使它產生函數提高。函數表達式不行,由於它不能提高

// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar());
function foo() { return 'foo' }
export {foo};

// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo());
function bar() { return 'bar' }
export {bar};

執行

$ node --experimental-modules a.mjs
b.mjs
foo
a.mjs
bar

例子2:

// even.js
import { odd } from './odd'
export var counter = 0;
export function even(n) {
  counter++;
  return n === 0 || odd(n - 1);
}

// odd.js
import { even } from './even';
export function odd(n) {
  return n !== 0 && even(n - 1);
}

執行

$ babel-node
> import * as m from './even.js';
> m.even(10);
true
> m.counter
6
> m.even(20)
true
> m.counter
17

m.even(20) 執行的時候,此時 counter 已經等於 6,加上本身執行的 11 次,因此一共是 17 次

相關文章
相關標籤/搜索