JavaScript

模組化程式設計

模組化設計

問題、解決問題

JQuery 時代

JQuery 時代,只要 $ 的變數在全局的作用域下,就可以在任何處呼叫做使用

方便的同時

也造成下方的問題:

  • 載入順序
  • 作用域的命名
  • 維護代碼變得更複雜

 

不同代碼間,存在隱形的依賴

無法得知 哪個函數屬於哪個腳本

模組化設計

解決方案:

  • 只在需要在需要的時候才載入模組,藉由此方式來建立模組間的依賴關係
  • 使用檔案路徑作為屬性的名稱,避免命名空間衝突
  • 使用 JavaScrip 函式的作用域來區分彼此模組的作用域
// module_system.js

// 引用模組
function require(path) {
    let mod = require.modules[path];
    mod.exports = {};
    mod.call(window, require, mod, mod.exports);
    return mod.exports;
}

// 存放模組位置
require.modules = {};


// 註冊模組
require.register = function (path, fn) {
    require.modules[path] = fn;
}

//-----------------------------------

// name.js
require
    .register('name.js', function (require, module, exports) {
        module.exports = 'shimingw'
    })


// hello.js
require
    .register('hello.js', function (require, module, exports) {
        let name = require('name.js')
        exports.hello_name = 'hello ' + name;
        exports.hello_world = 'hello world';
    })

模組化設計

藉由 module_system 的方法

註冊使用 模組

就能輕鬆的解決在使用模組化前

我們所碰到的問題了

模組化設計

var hello = require("hello.js");

console.log(hello.hello_name)


// output 'hello zhiwei'

使用方式

模組化設計

定義、規範

模組共同點

在開發各自的 模組 時,共同的步驟 與 共識

  • 定義模組
  • 定義依賴的模組
  • 模組必須具備耦合性 (可被任何模組引用

基於 第三點 的 耦合性

如果 模組 是可被通用的話

則一定會有所謂的 規範 或是 標準 存在

協定、標準

​如果有稍微好奇過 Javascript模組化 的相關資訊,你會發現關於這個話題還真有不少名詞

  • CommonJS

  • AMD

  • CMD

  • UMD

  • ES6

CommonJS

CommonJS

CommonJs 的核心非常簡單

// moduleA.js

module.exports = moduleA.someFunc;

導出:

// moduleA.js

const moduleA = require('./moduleA')

導入:

常見的環境與套件:

 

伺服器端

  • Common.js
  • .....

系統端

  • node.js

CommonJS

優點:

操作上不管是 開發 還是 引用 都十分的容易

因為在使用上就只有 requireexports 而已

缺點:

CommonJS 是使用 同步 的方式進行載入的

在系統端主機上直接使用這個方式是沒問題的,因為 模組 都在同一個位置上

但在 瀏覽器 上就不是這麼一回事,因為在客戶端,每隻腳本的載入時間是無法控制的

 

AMD

Asynchronous Module Definition

AMD

前面提到的 CommonJS  在 瀏覽器端 存在 同步 載入的問題,所以只能採用 非同步 載入

這就是 AMD 規範誕生的背景

 

AMD 是 Asynchronous Module Definition 的縮寫,意思就是 非同步模組定義

AMD

AMD也採用 require() 語句載入模組,但是不同於CommonJS,它要求兩個引數

如下:

require(['module1', 'module2'], function ( module1, module2 ) {
        // rest of your code here
        module1.doSomething();
});

AMD

看完如何載入後,來看一下要如何 定義 AMD 模組

// Define a module "myModule" with two dependencies, jQuery and Lodash
define("myModule", ["jquery", "lodash"], function($, _) {
    // This publicly accessible object is our module
    // Here we use an object, but it can be of any type
    var myModule = {};

    var privateVar = "Nothing outside of this module can see me";

    var privateFn = function(param) {
        return "Here's what you said: " + param;
    };

    myModule.version = 1;

    myModule.moduleMethod = function() {
        // We can still access global variables from here, but it's better
        // if we use the passed ones
        return privateFn(windowTitle);
    };

    return myModule;
});

AMD

常見的環境與套件

  • require.js
  • cmd.js
  • ......

UMD

Universal Module Definition

UMD

天啊~這麼還有啊~~

其實最主流的 標準 與 協定

就只有 CommonJS 與 AMD 兩種 而已

 

前面有說,模組是必須要有 耦合性

為了統一 CommonJS 與 AMD於是多出了UMD

UMD

// if the module has no dependencies, the above pattern can be simplified to
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define([], factory);
    } else if (typeof exports === 'object') {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory();
    } else {
        // Browser globals (root is window)
        root.returnExports = factory();
  }
}(this, function () {

    // Just return a value to define the module export.
    // This example returns an object, but the module
    // can return a function as the exported value.
    return {};
}));

其實 UMD 在代碼上,只是多了個 條件式 進行判斷,

判斷當前使用模組的方式是 CommonJS 還是 AMD

amd + commonjs + 全局变量

ES6 Module

ES6 Module

var basicNum = 0;

var add = function (a, b) {
    return a + b;
};

export { basicNum, add };
/** 引用模組 **/
import { basicNum, add } from './math';

function test(ele) {
    ele.textContent = add(99 + basicNum);
}

定義模組:

引用模組:

ES6 Module

// module.js
export default 123
<!-- index.html -->
<script type="module">
  import module from './module.js'
  console.log(module) // 123
</script>

<!-- index.html -->
<script type="module" src="index.js"></script>
// 控制台 123

在 HTML 上使用:

module.js 檔案

ES6 Module

特性:

  • 使用 import 被導入的 模組 在 嚴格模式 下 運行
  • 使用 import 被導入入的变量是 唯讀 的,可以理解默使用 const 進行宣告,無法被赋值
  • 使用 ES6 解構的特性,提升代碼的可讀性
  • ES6 module的语法是 靜態 的,更能清楚的表示 模組 間的依賴,提升 tree-shake 帶來的效益

ES6 Module

什麼是 tree-shake ?

tree shake 的本意其實就是透過 工具

我們的 js 的檔案,把不需要的東西給移除掉

參考資料

Javascript 模組化設計

By ZHI-WEI HUANG

Javascript 模組化設計

  • 63