模块化
什么是模块化?
百度百科中的解释: 模块化是指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程,有多种属性,分别反映其内部特性。
在编程中 模块化是将一个复杂的应用程序,按照一定的规则拆分成若干个文件(代码块),并进行组合。文件内部的数据与实现都是私有的,只是对外暴露一些接口(方法、变量)与其他模块进行通信
模块化的好处
- 避免命名空间的冲突
- 提高代码的复用性
- 提高维护性
- 更好的分离,实现按需加载
目前前端主流的模块规范是
CommonJS
ESModule
AMD
CMD
UMD
CommonJS
CommonJS
规范是一种同步加载模块的方式,其主要用于服务端,即 Node
中的
module.exports
用于规定当前模块对外输出的接口exports
是module.exports
属性的引用require
用于加载模块文件(读入并执行一个JavaScript
文件并返回该模块的exports
对象)
js
/* util.js */
const name = 'maomao'
exports.name = name
module.exports.log = function () {
console.log(name)
}
/* index.js */
const util = require('./util.js)
util.name
util.log()
exports 和 module.exports
exports
是module
对象的一个属性exports
是module.exports
的一个引用,在默认情况下module.exports
和exports
指向同一个空对象- 模块导出的是
module.exports
CommonJS 模块的特点
- 所有代码都运行在模块作用域,不会污染全局作用域
- 模块可以多次加载,但只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载时就直接读取缓存结果。要想让模块再次运行必须清除缓存
- 模块加载的顺序按其在代码中出现的顺序
ESModule
ESModule
是 ES6
在语言标准的层面上实现的模块功能,主要由 export
和 import
构成
export
命令用于规定模块的对外接口import
命令用于输入其他模块提供的功能
ESModule 与 CommonJS 的差异
CommonJS
是动态语法可以写在判断里;ESModule
静态语法只能写在顶层CommonJS
模块输出的是一个值的拷贝;ESModule
输出的是值的引用CommonJS
模块一旦输出一个值模块内部的变化就影响不到这个值ESModule
模块在JavaScript
引擎对脚本静态分析时,遇到模块加载命令import
,就会生成一个只读引用,等到脚本真正执行时再根据这个只读引用到被加载的那个模块里面去取值(ESModule
是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块)
CommonJS
模块是运行时加载;ESModule
是编译时输出接口。CommonJS
加载的是一个对象(即module.exports
属性),该对象只有在脚本运行完才会生成ESModule
不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成
CommonJS
模块的require()
是同步加载模块;ESModule
的import
命令是异步加载,有一个独立的模块依赖的解析阶段。- 顶层的
this
指向不同CommonJS
模块中的顶层this
指向模块本身ESModule
模块中的顶层this
指向undefined
- 模块的循环加载
CommonJS
模块在加载模块后就会执行整个脚本并在内存生成一个对象,当出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出ESModule
根本不会关心是否发生了"循环加载",只是生成一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
AMD
AMD
规范全称是 Asynchronous Module Definition,即异步模块定义,主要用于浏览器。AMD
规范完整描述了模块的定义、依赖关系、引用关系以及加载机制。其代表库是 RequireJS
相关 api 及使用
define()
: 定义模块require()
: 调用模块
js
/* 先通过 script 加载 RequireJS */
/* 定义模块 utils.js */
define(['utils'], function () {
function log() {}
return {
log: log,
}
})
/* 调用模块 */
require(['./utils'], function (utils) {
utils.log()
})
CMD
CMD
规范全称是 Common Module Definition,即通用模块定义,其代表库是 SeaJS
CMD
规范是在AMD
规范的基础上改进的一种规范,其解决了AMD
规范对依赖模块的执行时机的问题
相关 api 及使用
define()
: 定义模块seajs.use()
: 调用模块
js
/* 先通过 script 加载 SeaJS */
/* 定义模块 utils.js */
define(function (require, exports, module) {
function log() {}
return {
log: log,
}
})
/* 调用模块 */
seajs.use(['./utils.js'], function (utils) {
utils.log()
})
AMD 和 CMD 的区别
AMD
- 依赖前置: 在定义模块的时需要声明其依赖的模块
- 在加载模块完成后就会执行该模块,当所有模块都加载执行完后会进入 require 的回调函数执行主逻辑
CMD
- 就近依赖: 只有当用到某个具体模块时再去加载
- 加载完某个依赖模块后并不执行,当所有依赖模块加载完成后进入主逻辑,遇到 require 语句的时候才执行对应的模块
两者最大的区别是对依赖模块的执行时机处理不同
UMD
UMD
只是一种通用的写法,是为了解决当时存在多种流行而不统一的规范而产生的一种通用规范UMD
实际是 AMD + CommonJS + 全局变量
这三种规范的结合
js
;(function (root, factory) {
// CommonJs 模块规范 Node 环境
if (typeof module === 'object') {
module.exports = factory(require('jquery'))
}
// AMD 模块规范
else if (typeof define === 'function' && define.amd) {
define(['jquery'], factory)
}
// 挂载全局变量(global 即全局对象)
else {
root.returnExports = factory(root.jQuery)
}
})(this, function ($) {
// 定义属性
const name = 'jquery'
// 定义方法
function log() {}
// 暴露公共方法
return {
name,
log,
}
})