js 模組

hopper發表於2019-02-11
  • 模組,又被稱為元件/元件;模組化:把程式碼進行合理地分割成模組
  • 好的模組,是高度自包含,具有獨特的功能,允許它們在需要的時候被新增或移除,而且不會破壞整個系統

使用原因

有利於擴充套件和相互依賴的程式碼庫

  • 可維護性(maintainability)
  • 名稱空間(namespacing)
  • 可重用性(reusability)

合併模組到程式中

模組模式(Module Pattern)

注:javascript 中,函式是建立範圍的唯一方法

  • 例1.匿名閉包(Anonymous Closure)
(function () {
    var myGrade = [89, 91, 79, 65, 88]
    var average = function () {
        var total = myGrade.reduce((accumulator, item) => accumulator + item, 0)
        return `Your average grade is ${total/myGrade.length} .`
    }
    var failing = function () {
        var failGrades = myGrade.filter(item => item < 80)
        return `You failed ${failGrades.length} times.`
    }
    console.log(average())
    console.log(failing())
}())
複製程式碼
  • 例2.全域性匯入(global import)

類似匿名,但我們可以傳一個全域性變數作為引數

(function (globalVariable) {
    var _private = function () {
        console.log('this is private!')
    }
    
    // 遍歷陣列
    globalVariable.each = function (collection, iterator) {
        if (Array.isArray(collection)) {
            for (var i=0; i<collection.length; i++) {
                iterator(collection[i], i, collection)
            }
        } else {
            for (var key in collection) {
                iterator(collection[key], key, collection)
            }
        }
    }
    
    // 過濾
    globalVariable.filter = function (collection, test) {
        var filterResult = []
        globalVariable.each(collection, function (item) {
            if (test(item)) {
                filterResult.push(item)
            }
        })
        return filterResult
    }
    
    // 對映
    globalVariable.map = function (collection, iterator) {
        var mapResult = []
        globalVariable.each(collection, function (value){
            mapResult.push(iterator(value))
        })
        return mapResult
    }
    
    // 彙總
    globalVariable.reduce = function (collection, iterator, accumulator) {
        var startingValueMissing = accumulator === undefined
        globalVariable.each(collection, function (item) {
            if (startingValueMissing) {
                accumulator = item
                startingValueMissing = false
            } else {
                accumulator = iterator(accumulator, item)
            }
        })
        return accumulator
    }
}(globalVariable))
複製程式碼
  • 例3.物件介面(Object interface)

通過介面公開函式,同時將模組的實現隱藏在 function() 塊中

var gradeCaculate = (function () {
    // 私有變數
    var _grade = [89, 100, 82, 68, 93] 
    
    // 暴露函式
    return {
        average: function () {
            var total = _grade.reduce(function (accumulator, item) {
               return accumulator + item 
            }, 0)
            return `You average grade is ${total / _grade.length}`
        },
        failing: function () {
            var failingGrades = _grade.filter(function (item) {
                return item < 70
            })
            return `You failed ${failingGrades.length} items`
        }
    }
}())
複製程式碼
  • 例4.揭示模組模式(Revealing module pattern)

特點:確保所有方法和變數在公開之前保持私有

    var gradeCaculate = (function () {
        var _grade = [80, 100, 92, 68, 79]
        var _average = function () {
            var total = _grade.reduce(function (accumulator, item) {
                return accumulator + item
            }, 0)
            return `Your average grade is ${total / _grade.length}`
        }
        var _failing = function () {
            var failingGrades = _grade.filter(function (item) {
                return item < 70
            })
            return `You failed ${failingGrades.length} items`
        }
        
        // 暴露相關方法
        return {
            average: _average,
            failing: _failing
        }
    }())
    
複製程式碼

Commonjs and AMD

上述案例確實能起到作用,但是有它們的缺點:

  • 需要確保正確的依賴順序(script tag)
  • 它們仍然可能導致命名衝突

解決思想:設計一種方法訪問模組的介面而無需通過全域性的變數

CommonJs

  • 一個志願工作組設計和實現 javascript API ,用於申明模組。
  • CommonJs 模組實質上是一個可重用的 javascript,它匯出一個特定的物件,使其可供其程式中需要的其他模組使用(即用於匯入其他模組)
基本用法
  • javascript 檔案將模組儲存在自己獨特的模組上下文中
  • 使用 module.exports 物件公開模組
  • 使用 require 引入模組到需要的檔案中
// module.js
function myModule() {
    this.hello = function () {
        return 'hello'
    }
    this.goodbye = function () {
        return 'goodbye'
    }
}
module.exports = myModule

// require
var myModule = require('module.js')
var myModuleInstance = new myModule()
myModuleInstance.hello()
myModuleInstance.goodbye()
複製程式碼

相比之前描述的方式,這種方法有兩個明顯的優點:

  • 避免全域性名稱空間汙染
  • 使我們的依賴更加精確

注:CommonJs 採用伺服器優先載入;而且是同步載入模組(適用服務端渲染,案例:nodejs)

AMD(Asynchronous Module Definition)

非同步載入模組

  • 使用 AMD
    • 需要使用 define 關鍵字定義模組
    • define 函式使用每個模組的依賴項的陣列作為第一個引數
    • 依賴以非阻塞的方式在後臺載入
    • 載入完,呼叫定義在 define 的函式(即第二個引數f)
      • 在回撥中使用已載入的模組(如:myModule, otherModule)
// 定義模組。myModule
define([], function () {
    return {
        hello: function () {
            return 'hello world'
        }
    }
})

// 在模組中載入其他模組
define(['myModule', 'otherModule'], function (myModule, otherModule) {
    myModule.hello()
})
複製程式碼

與 CommonJs 不同,AMD 採用瀏覽器優先和非同步方式來完成工作;而且 AMD 定義的模組型別可以是物件、字串、建構函式、JSON和其它型別,而 CommonJs 的只能是物件。

但是,AMD 不適用 io 、檔案系統和麵向伺服器的功能,這些只能通過 CommonJs 獲得。

UMD(universal module definition)

  • 同時支援 AMD 和 CommonJs 的功能
    • UMD 本質上建立了一種方法來使用兩者中的任何一種,同時還支援全域性變數定義
  • 能夠在客戶端和服務端起作用
// 相容 AMD 、CommonJs、全域性變數
// UMD example:https://github.com/umdjs/umd

(function (root, factory) {
    // root -> this
    if (type define === 'function' && define.amd) {
        // AMD. define 的 amd 屬性,區別於自定義的 define 函式
        define(['myModule', 'otherModule'], factory(myModule, otherModule))
    } else if (typeof module === 'object' && typeof module.exports === 'object') {
        // CommonJs
        module.exports = factory(require('myModule'), require('otherModule'))
    } else {
        // 全域性變數
        root.returnExports = factory(root.myModule, root.otherModule)
    }
}(this, function (myModule, otherModule) {
    // 模組的功能。依賴的模組:myModule、otherModule
    const _pravite = () => {
        console.log('I am pravite')
    }
    const sayHello = () => {
        console.log('hello')
    }
    const sayGoodbye = () => {
        console.log('goodbye')
    }
    return {
        sayHello,
        sayGoodbye
    }
}))
複製程式碼

NativeJs

  • 上述討論中,已經建立使用模組模式、CommonJs 和 AMD 來模擬模組系統的方法
  • 但是,上面的模組不是 javascript 原生的
  • ECMAScript 6 (ES6) 已經有內建模組

ES6 模組的優勢

相比於 CommonJs 或 AMD

  • 緊湊和宣告性語法
  • 非同步載入
  • 更好地支援迴圈依賴
與 CommonJs 的區別
  • ES6 模組的匯入是匯出的實時只讀檢視
  • CommonJs 的匯入是匯出的副本

CommonJs 例子

// js/counter.js
var counter = 1 
function increment () {
    counter ++
}
function decrement () {
    counter --
}
module.exports = {
    counter: counter,
    increment: increment,
    decrement: decrement
}

// src/main.js
var counter = require('../../js/counter.js')
counter.increment()
console.log(counter.counter) // 1

// 這個例子產生了兩個模組副本,exports 時一個,require 時一個
// 在 main.js 的副本,和原始模組的副本斷開連線。執行 increment 方法增加的原始模組(counter.js)的 counter 值,因此執行完輸出的 counter 依然為 1

// 如果想輸出的 counter 增加 1 ,處理如下:
// src/main.js
counter.counter++
console.log(counter.counter) // 2
複製程式碼

ES6 例子

// js/counter.js
// 注:匯出的時候需要申明
export let counter = 1
export function increment () {
    counter++
}
export function decrement () {
    counter--
}

// src/main.js
import * as counter from '../../counter'
console.log(counter.counter) // 1
counter.increment()
console.log(counter.counter) // 2
// 實時更新匯入模組的值
複製程式碼

參考資料

相關文章