模塊化規範--入門 CommonJS、AMD、CMD、ES6 模塊化

JS模塊化

模塊化發展

  1. 模塊化前
    • 項目壯大時,代碼功能不明顯,不利於維護
    • 容易污染全局變量
    • html 中引入過多的 js 文件,且不清楚文件依賴
  2. 模塊化的優點
    • 避免命名衝突
    • 代碼分離,實行按需加載
    • 更好的複用和維護代碼
    • 解決引入js庫時依賴模糊的狀況
  3. 模塊化思路
    • 根據代碼功能拆分模塊

IIFE 實現模塊化

  模塊化出現以前,實現模塊化的一種方法是使用當即執行函數(IIFE),在 window 上添加屬性javascript

  • 向IIFE添加依賴,爲window添加屬性

JS:css

// index.js
(function(window) {         // window 是全局變量,可缺省
    let str = 'Hello World!'
    let foo = function() {
        console.log(msg)
    };
  window.module = { foo }
})(window)

HTML:html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript" src="index.js"></script>
    <script type="text/javascript">
        module.foo()
    </script>
</body>
</html>
  • 能夠向IIFE中添加更多的依賴

JS:java

// index.js
(function(window, $) {
  const str = 'hello world!'
  const foo = function() {
    console.log(str)
  }
  const bar = function() {
    console.log('change background color...')
    $('body').css('background', 'red')
  }
  window.module = {
    foo,
    bar
  }
})(window, jQuery)  // 注入 jQuery

​ HTML:node

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript" src="jquery-3.1.1.min.js"></script>
    <script type="text/javascript" src="index.js"></script>
    <script type="text/javascript">
    module.foo()
    module.bar()
    </script>
</body>
</html>

缺點:react

  • 依賴過多時,html 中 <script> 的引入順序不能改變,不然出錯
  • window 上添加過多的屬性,看起來臃腫且很差維護

模塊化規範

1.CommonJS

  CommonJS 能夠在服務器端或瀏覽器端實現。jquery

  • 服務器端:利用 Node.js,運行時動態加載模塊(同步)
  • 瀏覽器端:利用 Browserify.js ,在轉譯(編譯)時加載打包模塊(生成新文件),修改模塊文件後需從新編譯打包

語法 :es6

  暴露模塊,至關於暴露一個空對象,單獨使用 exports.someVar 至關於在該空對象上添加成員,使用 module.exports = { ... } 則用新的對象(也能夠是變量或者函數等)代替空對象express

// 1.僅能使用一次,重複使用會覆蓋
module.exports = { ... };
module.exports = function() { ... };
module.exports = 'someVar';

// 2.能屢次使用,默認將全部內容整合成一個對象,至關於 1 中暴露對象
exports.foo = function() { ... };
exports.bar = 'someVar';
// 等同於
module.exports = {
    foo: function() { ... },
    bar: 'someVar'
}

  引入模塊,將模塊所暴露的對象(也能夠是一個單獨的變量或者函數等)引入npm

// 第三方模塊,直接輸入模塊名
let m1 = require('react');
// 自定義模塊,相對路徑
let m2 = require('./module.js');
// 調用:根據暴露方式調用

1.在服務器端中使用

// module_1.js
exports.str = 'hello world!'
exports.num = 100

// module_2.js
exports.foo = function() {
    console.log('foo() in module_2.js')
}

// module_3.js
module.exports = {
    bar: function() {
        console.log('bar() in module_3.js')
    }
}

// module_4.js
module.exports = {
    bar: function() {
        console.log('bar() in module_3.js')
    }
}

// main.js
const module_1 = require('./module_1');
const module_2 = require('./module_2');
const module_3 = require('./module_3');
const module_4 = require('./module_4');

console.log(module_1.str, module_1.num);
module_2.foo();
module_3.bar();
module_4();

運行 node main.js 的結果:

hello world! 100
foo() in module_2.js
bar() in module_3.js
baz() in module_4.js

2. 在瀏覽器端中使用

  在瀏覽器中使用時,要安裝依賴包 browserify.js

1)下載安裝 browserify.js

// 生成 package.json
npm init
// 全局安裝
npm install browserify -g
// 項目目錄中安裝並添加至 package.json
npm install browserify --save-dev

2)基礎目錄

|-project
  |-dist    // 打包生成文件目錄(注:browserify 不會自動生成文件夾,須要手動建立)
  |-src                 
    |-main.js    // 主文件
    |-module1.js 
    |-module2.js
    |-module3.js
    |-module4.js
  |-index.html   
  |-package.json  // 項目配置文件
  |-node_modules  // 依賴包

  其中各 js 文件代碼同上

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript" src="dist/build.js"></script>
</body>
</html>

使用 browserify 進行模塊打包:

$ browserify ./src/main.js -o ./dist/build.js

在瀏覽器的控制檯能看到相同的輸出:

hello world! 100
foo() in module_2.js
bar() in module_3.js
baz() in module_4.js

2.AMD

 AMD(Asynchronous Module Definition異步模塊定義) 專用於瀏覽器端,是 異步 加載模塊的。依賴於 require.js

語法步驟:

  1. 暴露模塊,一般爲暴露一個對象,對象包裝了一些數據
// 1.定義沒有依賴其餘模塊的模塊, module_1.js
define(function() {
  const str = 'hello world'
  const foo = function () {
    return str
  }
  return {
    foo: foo,
    bar: function() {
      console.log('bar() in module_1.js')
    }
  }
})
// 2.定義依賴其餘模塊的模塊, module_2.js
// 第一個參數數組是此模塊依賴的全部模塊,並要以形參傳入後一個參數函數
define(['module_1'], function(m1) {
  const baz = function() {
    console.log(m1.foo(), 'baz() in module_2.js')
  }
  return { baz }
});
  1. 引入模塊和模塊配置,require 也能夠用 requirejs
// main.js
(function() {
    // 模塊配置:paths 爲各模塊路徑配置,還能夠配置更多的選項,如 baseUrl 等
  require.config({
        // 注意:模塊路徑不能添加 .js 後綴,引入第三方庫時(jQuery, Angular等),名字必須對應並且爲小寫。
    paths: {
      module_1: './modules/module_1',
      module_2: './modules/module_2',
      jquery: './libs/jquery-3.1.1.min'
    }
    });
    // 引入並使用模塊
  require([
    'module_1',
    'module_2',
    'jquery'
  ], function(m1, m2, $) {
    m1.bar();
    m2.baz();
    $('body').css('background', 'red')
  });
})();
  1. 在 HTML 文件的引入
<body>
    <!-- data-main:應用源文件main.js的路徑,src:require.js的路徑 -->
    <script data-main="./js/main.js" src="./js/libs/require.js"></script>
</body>

完整步驟

1)下載

  在官網下載 RequireJS,保存至文件 require.js

2)基礎項目目錄

|-project
  |-js
    |-libs    // 庫文件夾
      |-require.js
      |-jquery-3.1.1.min.js
    |-modules    // 模塊文件夾
      |-module_1.js
      |-module_2.js
    |-main.js    // 主文件
  |-index.html

各 js 文件的內容如語法步驟所示。

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <!-- 
        src:require.js的路徑
        data-main:應用源文件main.js的路徑
     -->
    <script type="text/javascript" data-main="js/main.js" src="./js/libs/require.js"></script>
</body>
</html>

3)在瀏覽器控制檯的輸出:

bar() in module_1.js
3 hello world baz() in module_2.js
// 且頁面背景色爲紅色

3.CMD

  CMD(Common Module Definition通用模塊定義),依賴於 sea.js 庫。

語法:

  實現語法相似於 browserify.js 和 require.js 的結合。並能夠執行異步加載。

暴露模塊

// 只要暴露了任何內容的模塊,function 都要帶這三個形參
define(function(require, exports, module) {
    let data = 'in module-1';
    let foo = function() {
        console.log(data);
    };
    // 暴露內容的語法相似於 browserify
    // 1.暴露一個對象
    module.exports = { foo };
    // 2.暴露一個方法
    module.exports = foo;
    // 3.分開暴露多個屬性
    exports.data = data;
    exports.foo = foo;
});

引入模塊

// 模塊再也不暴露內容時,只寫一個形參 require
define(function(require) {
    // 同步引入
    let module1 = require('./module1');
    module1.foo();
    let module4 = require('./module4');
    module4.baz();
    // 異步引入
    require.async('./module3', function(module3) {
            module3();
    })
});

完整步驟

1)下載

  官網下載 sea.js。保存至 'js/libs/sea.js'

2)基礎項目目錄

|-js
  |-libs
    |-sea.js
  |-modules
    |-module1.js
    |-module2.js
    |-module3.js
    |-module4.js
  |-|-main.js
|-index.html

3)文件

module1.js:

define(function(require, exports, module) {
  const str = 'In module_1.'
  exports.fun1 = function() {
    console.log(str)
  }
})

module2.js:

define(function(require, exports, module) {
  const module_1 = require('./module_1')
  module_1.fun1()

  const fun2 = function() {
    console.log('In module_2.')
  }

  module.exports = {
    fun2: fun2
  }
})

module3.js:

define(function(require, exports, module) {
  const fun3 = function() {
    console.log('In module_3.')
  }
  module.exports = fun3
})

module4.js:

define(function(require, exports, module) {
  const module_2 = require('./module_2')
  module_2.fun2()
  // 異步加載
  require.async('./module_3', function(m3) {
    m3()
  })
  
  module.exports = {
    fun4: function() {
      console.log('In module_4.')
    }
  }
})

main.js:

define(function(require) {
  const module_4 = require('./modules/module_4')
  module_4.fun4()
})

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <!-- 先引入 sea.js -->
    <script src="./js/libs/sea.js"></script>
    <script>
        // 加載模塊
        seajs.use('./js/modules/main.js');
    </script>
</body>
</html>

4)在控制檯的輸出:

In module_1.
In module_2.
In module_4.
In module_3.

4.ES6

  ES6 模塊化依賴於 babel (轉換爲ES5語法) 和 browserify.js (編譯打包模塊),是靜態加載的。

語法:

暴露模塊:

  • 使用 exportexport default
  • 使用 export 能夠逐個暴露變量,可是命令要包含對外的接口;
  • 使用 export default 定義一個默認暴露的對象(其中 default 就至關於對外的接口);
  • 兩者能夠同時使用,可是 export default 只能使用一次;
// 1.逐個暴露,暴露兩個對外接口 arr 和 foo
export let arr = [1, 2, 3];
export function foo() { return 'Hello World!'; };

// 2.一次性暴露,1 的另外一種寫法
let str = 'Hello World!';
let bar = function() { console.log(str); };
export { str, bar };

// 3.暴露默認模塊(只能定義一個默認模塊,定義多個至關於覆蓋)
export const PI = 3.14  // 暴露一個對外接口 PI
let fun1 = function() { return true; };
let fun2 = function() { return false; };
export default { fun1, fun2 }   // 默認暴露一個對象

引入模塊:

  • 使用 import 引入模塊(至關於打開與模塊的接口通道,從中取出所需模塊);
  • export 暴露的變量的引入,要一一對應其對外接口;
  • export default 暴露的變量的引入,能夠用別名指定(實質上是對 default 接口的重命名);
  • 還能夠使用 * 指定引入模塊的全部非默認(default)暴露的對外接口,使用 as 對對外接口重命名;
  • import 具備提高功能,不要求在代碼的頂部;
  • import 不能動態引入模塊,由於它是靜態加載的;
  • importexport 能夠一塊兒使用,具備 "轉發" 模塊的功能,能夠用做各模塊的整合或者跨模塊變量。
  • 引入的變量是隻讀的,不能修改其值;對象變量的屬性能夠修改,可是也不要使用,避免產生混亂
// 1.引入自定義模塊:非默認暴露
import { foo, bar } from './module1.js';  // .js 後綴能夠省略
import * as m1 from './module1.js';  // 引入模塊的全部對外接口,可以使用 m1.foo, m1.bar

// 2.引入自定義模塊:默認暴露
import module3 from './module3.js';  // 不能添加 {}

// 3.引入第三方模塊
import $ from 'jquery';   // npm 下載的第三方包,路徑自動調至 node_modules

// 4.與 export 一塊兒使用
// index.js
export {exp} from './math_module.js'
export {data} from './data_module.js'
// script.js
import {exp, data} from './index.js'

使用步驟

1)安裝

// 初始化 package.json
$ npm init
// 全局安裝依賴庫
$ npm install babel-cli -g  // babel 命令行接口
$ npm install babel-preset-es2015 -g  // 用於轉譯 ES6 代碼的庫
$ npm install browserify -g  // 編譯打包 ES6 模塊
// 添加項目依賴
$ npm install babel-cli --save-dev
$ npm install babel-preset-es2015 --save-dev
$ npm install browserify --save-dev
// 有必要時,安裝第三方依賴包
$ npm install jquery@1 --save-dev

2)基本項目目錄

|-js    
  |-build  // 用 babel 轉譯爲 es2015 語法的模塊文件時生成,不須要本身建立文件夾
    |-main.js
    |-module1.js
    ...
  |dist  // browserify 打包後的文件,須要本身建立文件夾
    |-bundle.js 
  |-src  // 模塊文件
    |-main.js
    |-module1.js
    |-module2.js
    |-module3.js
  |-node_modules
  |-index.html
  |-.babelrc  // babel 配置文件,一個 json 文件,不能添加更多後綴
  |-package.json
  |-package-lock.json

3)文件

.babelrc

{
    "presets": ["es2015"]
}

module1.js

// 分別暴露多個對外接口
export const str = 'hello world!'
export const num = 100
export function fun1() {
  console.log('In module_1.js')
}
// 等同於
/* 
const str = 'hello world!'
const num = 100
function fun1() {
  console.log('In module_1.js')
}
export { str, num, fun1 } 

*/

module2.js

// 暴露一個對外接口
export function fun2() {
  console.log('In module_2.js')
}
// 一個默認暴露
export default function() {
  console.log('export default in module_2.js')
}

module3.js

// 轉發來自 module_1 和 module_2 的對外接口
export { str, num, fun1 } from './module_1'
export { fun2 } from './module_2'
import dm2 from './module_2'
// 默認暴露一個自身的對外接口
export default {
  str: 'module_3',
  fun3: function() {
    console.log(`In ${this.str}.js`)
  },
  dm2
}

main.js

// import * as m3 from './module_3'
// import dm3 from './module_3'
// 能夠寫爲
import dm3, * as m3 from './module_3'  // dm3 接收默認暴露;m3 爲別名,接受全部非默認暴露的接口
import $ from 'jquery'  // 第三方庫

m3.fun1()
m3.fun2()
dm3.fun3()
dm3.dm2()
console.log(m3.str, m3.num)
$('body').css('background', 'red')

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="./dist/bundle.js"></script>
</body>
</html>

4)輸出

In module_1.js
In module_2.js
In module_3.js
export default in module_2.js
hello world! 100
// body 背景色爲紅色

對比

CommonJS Module 和 ES6 Module

區別:

  • 暴露模塊的語法上相似,exportsexportmodule.exportsexport default;可是含義卻不同,module.exports 會覆蓋其餘單獨暴露的語句,export default 只是一個額外的默認暴露,不影響單獨暴露的語句
  • CommonJS Module 是運行時加載的,ES6 Module 是編譯時加載接口(靜態)
  • CommonJS Module 輸出的是一個值的拷貝(帶緩存功能),ES6 Module 輸出的是值的引用

ES6 Module 加載 CommonJS Module:

  • 三種方法獲取 module.exports,使用第三種方法時,要經過 foo.default() 才能獲取真正的 module.exports
// a.js
module.exports = function() {
  console.log('hello world!')
}

// 法一
import foo from './a.js'
// foo = function() {}
foo()

// 法二
import { default as foo } from './a.js'
// foo = function() {}
foo()

// 法三
import * as foo from './a.js'
// foo = { default: function() {} }
foo.default()
  • 因爲 ES6 Module 是編譯時肯定輸出接口,CommonJS Module 是運行時肯定輸出接口;這就說明了引入 CommonJS Module 時,要總體引入
// 法一
import * as express from 'express';
const app = express.default();

// 法二
import express from 'express';
const app = express();
相關文章
相關標籤/搜索