模块通常指的是一个独立的可重用软件组件,它包含了一组相关的函数、方法、类、变量等,能够实现特定的功能或提供特定的服务。在编程中,可以将程序的功能划分成不同的模块,每个模块负责不同的任务
JavaScript的模块化是一种将代码组织到独立的、可重用的、可维护的单元中的方式,使代码更易于管理和复用。模块化可以帮助开发者将代码分解成更小的部分,这样更容易理解和维护,同时也可以防止命名冲突和全局污染。在JavaScript中,有多种模块化的标准和实现,如CommonJS、AMD、ES6等。其中ES6的模块化已经成为JavaScript的官方标准,可以直接在浏览器和Node.js中使用。
将可复用的变量/函数直接赋值给window;
存在的问题:污染全局命名空间, 容易引起命名冲突或数据不安全,而且模块成员之间看不出直接关系
window.fun1 = function(){
...
}
window.fun2 = function(){
...
}
简单对象封装,减少了全局变量,解决命名冲突
存在的问题:数据不安全(外部可以直接修改模块内部的数据)
let myModule= {
data: '123',
fun1(){
console.log(this.data)
}
}
myModule.data = "abc" //能直接修改模块内部的数据
myModule.fun1() // abc
简单来说就是匿名函数的自调用。在函数内容将数据私有化, 通过给window添加属性来向外暴露接口,外部只能通过暴露的方法获取/操作数据;
存在的问题:如果当前模块需要依赖其他模块时,无法处理。
// index.html文件
// module.js文件
(function(window) {
let data = '123'
// 操作数据的函数
function setData(value) {
data = value
}
function getData() {
otherFun() //内部调用
return data
}
// 内部私有的函数
function otherFun() {
console.log('otherFun()')
}
// 暴露函数
window.myModule = { setData, getData } //ES6写法
})(window)
解决了IIFE模式无法引入其他模块的问题,是现代模块实现的基石。
// index.html文件
// module.js文件
(function(window, $){
let data = "123"
// 操作数据的函数
function setData(value) {
data = value
}
function getData() {
otherFun() //内部调用
return data
}
// 内部私有的函数
function otherFun() {
console.log('otherFun()')
}
// 修改背景颜色
function updateBG(){
// 设置随机颜色
var randomColor = Math.floor(Math.random()*16777215).toString(16);
if((''+randomColor).length<6) randomColor=randomColor+'0';
$('body').css('background', '#'+randomColor)
}
// 暴露函数
window.myModule = { setData, getData, updateBG } //ES6写法
})(window, jQuery)
避免命名冲突(减少命名空间污染);
更好的分离, 按需加载;
更高复用性;
高可维护性;
与CommonJS相比,可以得出AMD模块定义的方法非常清晰,不会污染全局环境,能够清楚地显示依赖关系。AMD模式可以用于浏览器环境,并且允许非同步加载模块,也可以根据需要动态加载模块。
CMD 是Common Module Definition(通用模块定义)的缩写;
CMD规范同样专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。CMD规范整合了CommonJS和AMD规范的特点。
Sea.js:一个遵循CMD规范的JavaScript模块加载框架。
官网: http://seajs.org/
github : GitHub - seajs/seajs: A Module Loader for the Web
// 定义模块
// 1.定义没有依赖的模块
define(function(require, exports, module){
// 暴露方式跟CommonJS一致
exports.xxx = value
module.exports = value
})
// 2.定义有依赖的模块
define(function(require, exports, module){
//引入依赖模块(同步)
var module1 = require('./module1')
//引入依赖模块(异步)
require.async('./module2', function (module2) {})
exports.xxx = value
})
// 引入使用的模块
define(function (require) {
var m1 = require('./module1')
var m4 = require('./module4')
m1.fun()
m4.fun()
})
实例:
// modeule1.js
define(function(require, exports, module){
let data = "abc"
function getData(){
return data.toUpperCase()
}
exports.getData = getData
})
// modeule2.js
define(function(require, exports, module){
module.exports = {
name: 'zhangsan'
}
})
// modeule3.js
define(function(require, exports, module){
var m2 = require("./module2")
function showMsg(){
console.log("Hello " + m2.name)
}
module.exports = { showMsg }
})
// main.js
define(function(require){
//引入依赖模块(同步)
var m3 = require("./module3")
m3.showMsg()
//引入依赖模块(异步)
require.async("./module1", function(m1){
let data = m1.getData()
console.log("Data:", data)
})
})
// index.html
// CMD
define(function (requie, exports, module) {
//依赖就近书写
var module1 = require('Module1');
var result1 = module1.fun();
module.exports = {
result1: result1,
}
});
// AMD
define(['Module1'], function (module1) {
var result1 = module1.fun();
return {
result1: result1,
}
});
从上面的代码比较中我们可以得出AMD规范和CMD规范的区别
对依赖的处理:
AMD推崇依赖前置,即通过依赖数组的方式提前声明当前模块的依赖;
CMD推崇依赖就近,在编程需要用到的时候通过调用require方法动态引入;
2.在本模块的对外输出:
AMD通过返回值的方式对外输出;
CMD通过给module.exports赋值的方式对外输出;
UMD 是Universal Module Definition(通用模块定义)的缩写;
UMD 是一种javascript通用模块定义规范,让你的模块能在javascript所有运行环境中发挥作用。这就意味着要同时满足CommonJS, AMD, CMD的标准。
实现:
(function(root, factory) {
if (typeof module === 'object' && typeof module.exports === 'object') {
console.log('是commonjs模块规范,nodejs环境')
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
console.log('是AMD模块规范,如require.js')
define(factory)
} else if (typeof define === 'function' && define.cmd) {
console.log('是CMD模块规范,如sea.js')
define(function(require, exports, module) {
module.exports = factory()
})
} else {
console.log('没有模块环境,直接挂载在全局对象上')
root.umdModule = factory();
}
}(this, function() {
return {
name: '我是一个umd模块'
}
}))
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
// 定义模块 math.js
var num= 0;
var add = function (a, b) {
return a + b;
};
export { basicNum, add };
// 引用模块
import { num, add } from './math';
function test() {
let res = add(99 + num)
console.log(res)
}
如上例所示,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。
// export-default.js
export default function () {
console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'
模块默认输出, 其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。
3.5.3、ES6 模块与 CommonJS 模块的差异
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用;
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD CMD解决方案;
AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅;
CMD规范与AMD规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在Node.js中运行;
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案;
UMD为同时满足CommonJS, AMD, CMD标准的实现;
参考文章:
1.CommonJS详解 - 简书
2.JS模块规范大盘点:CommonJS, AMD, CMD, UMD 和 ES6 Modules 规范与源码实现 - 知乎 .
3.ES6 入门教程