一、概念
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分为若干模块的过程,模块是可组合、分解和更换的单元。
编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。
1. 模块化的好处
- 提高了代码的 复用性
- 提高了代码的 可维护性
- 可以实现 按需加载
2. 模块化规范产品
es6
之前的模块化规范有:
规范 | 实现产品 |
---|---|
CommonJS | NodeJS、Browserify |
AMD | requireJS |
CMD | seaJS |
二、ES6 模块化
想要使用 es6
的模块化,则必须申明 <script type="module"></script>
标签的 type
属性值为 module
<script type="module" src="../xxx.js"></script>
1. 暴露语法汇总
1.1. 默认暴露
一个文件中只可以有一个默认暴露
// m1.js
export default {
SUCCESS: 0,
getCodeMessage() {
console.log('默认暴露')
}
}
1.2. 分别暴露
在申明的 变量
或 方法
前增加关键字 export
// m2.js
export const SUCCESS = 0
export function getCodeMessage() {
console.log('分别暴露')
}
1.3. 统一暴露
// m3.js
const SUCCESS = 0
function getCodeMessage() {
console.log('统一暴露')
}
export {
SUCCESS, // SUCCESS: SUCCESS
getCodeMessage // getCodeMessage: getCodeMessage
}
2. 引入语法汇总
2.1. 通用引入
不管使用什么方式向外暴露,都可以使用 通用引入
<script type="module">
// 默认暴露
import * as m1 from './modules/m1.js'
// 分别暴露
import * as m2 from './modules/m2.js'
// 统一暴露
import * as m3 from './modules/m3.js'
m1.default.getCodeMessage()
m2.getCodeMessage()
m3.getCodeMessage()
</script>
- 使用该种方式导入
默认暴露
的模块,前面必须得加default
才行 - 可以看到,对于
分别暴露
与统一暴露
的模块,导入时的用法使用一致
2.2. 解构赋值形式
<script type="module">
// 默认暴露(因为 default 是关键字,所以必须得起别名)
import {default as m1} from './modules/m1.js'
// 分别暴露
import {SUCCESS, getCodeMessage} from './modules/m2.js'
// 统一暴露(因为变量不能重复,可以使用别名)
import {SUCCESS as a, getCodeMessage as fn} from './modules/m3.js'
m1.getCodeMessage()
getCodeMessage()
fn()
</script>
2.3. 简便形式
只能针对 默认暴露
使用
<script type="module">
// 默认暴露
import m1 from './modules/m1.js'
m1.getCodeMessage()
</script>
3. 其他说明
-
分别暴露
与统一暴露
更推荐使用统一暴露
,因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量 -
import
命令具有提升效果,会提升到整个模块的头部先执行foo(); import { foo } from './my_module.js';
上面的代码不会报错,因为
import
的执行早于foo
的调用。这种行为的本质是,import命令是编译阶段执行的,在代码运行之前 -
import
是静态执行,所以不能使用表达式和变量,以下代码会报错// 报错 import { 'f' + 'oo' } from './my_module.js'; // 报错 let module = './my_module.js'; import { foo } from module; // 报错 if (x === 1) { import { foo } from './module1.js'; } else { import { foo } from './module2.js'; }
-
如果多次重复执行同一句
import
语句,那么只会执行一次,而不会执行多次import './lodash.js'; import './lodash.js';
-
顶层
this
是undefined
三、CommonJS 模块化
1. 概念
- 每个模块内部,
module
变量代表当前模块 - module 变量是一个对象,
module.exports
是对外的接口 - 加载某个模块即加载该模块的
module.exports
属性 - 在服务端:模块的加载是
同步
的 - 在浏览器端:模块需要提前编译打包处理(需要用到
browserify
)
2. module.exports 导出
- 在自定义模块中,可以使用
module.exports
对象,将模块内的成员共享出去,供外界使用。 - 外界用
require()
方法导入自定义模块时,得到的就是module.exports
所指向的对象。
// 向 module.exports 对象上挂载属性
module.exports.username = 'admin'
// 向 module.exports 对象上挂载方法
module.exports.getUserInfo = () => {}
3. exports 导出
由于 module.exports
单词写起来比较复杂,为了简化向外共享成员的代码,Node 提供了 exports
对象。默认情况 下,exports
和 module.exports
指向同一个对象
使用 require() 方法导入模块时,导入的结果,永远以
module.exports
指向的对象为准。
/* 案例一 */
exports.a = 1
module.exports = {
b: 2
}
// 最终导出的结果为 { b: 2 }
/* 案例二 */
module.exports.a = 1
exports = {
b: 2,
c: 3
}
// 最终导出的结果为 { a: 1 }
/* 案例三 */
module.exports.a = 1
exports.b = {
id: 10
}
// 最终导出的结果为 { a: 1, b: { id: 10 } }
4. require 引入
- 内置模块或第三发模块:
xxx
指的是包名 - 自定义模块:
xxx
指的是模块的路径
5. 其他说明
- 对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值
- 对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。
- 当使用require命令加载某个模块时,就会运行整个模块的代码。(执行时运行)
- 当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
- 循环加载时,属于加载时执行。即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。
- 顶层
this
指向所在模块
四、browserify
让服务器端的 CommonJS
格式的模块可以运行在浏览器端。
1. 安装
全局和局部必须都安装才能使用
- 全局:
npm install browserify -g
- 局部:
npm install browserify --save-dev
2. 运行
# 格式说明:browserify 要执行的文件路径 -o 编译后放置的文件路径
browserify src/app.js -o dist/budle.js