- 模块,又被称为元件/组件;模块化:把代码进行合理地分割成模块
- 好的模块,是高度自包含,具有独特的功能,允许它们在需要的时候被添加或移除,而且不会破坏整个系统
使用原因
有利于扩展和相互依赖的代码库
- 可维护性(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
// 实时更新导入模块的值