- 模組,又被稱為元件/元件;模組化:把程式碼進行合理地分割成模組
- 好的模組,是高度自包含,具有獨特的功能,允許它們在需要的時候被新增或移除,而且不會破壞整個系統
使用原因
有利於擴充套件和相互依賴的程式碼庫
- 可維護性(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
// 實時更新匯入模組的值
複製程式碼