模块化
未模块化带来的问题:
浏览器会默认将不同 JS 文件(包含外联 JS 文件和嵌入式 JS)合并为一个模块(除了设置
type="modules"
的外部 JS 文件),那么这将易导致三个问题:全局变量污染,各个文件中的函数和变量都将存在于全局作用域内。
各个来源之间的 JS 函数或全局作用域内的变量可能存在冲突。
各个 JS 文件必须遵循引用关系排列,但是单从 JS 文件很难分辨模块之间的依赖关系。
ES6 模块化
为了解决以上问题,ES6 标准化了 模块化
,具体实现即是 export
与 import
。
常见模块化思想:将不同功能的 JS 函数封装在不同的 JS 文件中,export
将模块的公有方法暴露给模块外部, import
引入当前模块的外部依赖,即明确当前模块依赖哪些模块。各个模块各司其职。此举便于后期维护,也便于他人加入开发。
// utils.js 底层工具函数
export function isArray (target) {
return Array.isArray(target)
}
// module-utils.js 模块工具函数
import { isArray } from './utils'
export default function formatData (target) {
// target 进行一些处理后,赋值给 newTarget
return isArray(newTarget)
}
// module-a.js 业务逻辑
import formatData from './module-utils'
const arr = [1, 2, 3, 4]
// arr 经过一些处理,赋值给 newArr
formatData(newArr)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
在示例中,依据每个 JS 文件默认都是一个独立的模块(都拥有自己独有的作用域)的原理,那么示例中可看出存在 utils
、module-utils
、module-a
这三个模块。在开发过程中,他们各司其职,utils
其中主要包含开发时可能被许多模块用到的底层函数,module-utils
主要包含在开发某个模块多次使用的再次被包装的工具函数,module-a
即是开发的业务模块。以上分类标准并非是开发的强制的分类模块标准,即如何分类不是最重要的,如何分离模块是需要结合实际开发情况来判断的,最重要的是分离模块开发的这种开发思维。
有了以上的分工,极大提高了代码健壮性,每个模块都有自己的功能,每个模块对外暴露自己公有方法,模块内部的一些重要变量也得以保护。防止了工具函数及其关联变量被意外修改的发生。
AMD( Asynchronous Module Definition )
AMD
(github wiki) 规范表示 异步模块定义 规范。
常用的 AMD
实现之一有 requirejs
(官网)工具库,它会在全局作用域中暴露 define
(定义模块)和 require
函数(加载模块)。requirejs
实现了异步或动态加载依赖的模块机制。它只会加载被依赖的模块。
requirejs
的引入与入口
<script data-main="scripts/main" src="scripts/require.js" async></script>
示例中,data-main
表示,整体业务代码的逻辑入口,即主模块,所有代码的开端。
定义模块
/**
* API define
* @param {String} id 模块名
* @param {Array<String>} dependence 当前定义的模块的依赖模块,数组中的每一项为模块
* 地址的字符串
* @param {Function/Object} fn_Or_Object 回调函数的返回值或一个对象定义当前模块的值
*/
define(id, dependence, function (m1, m2) {
// 通过回调函数的 return 定义模块的值
return function () {}
})
// 不依赖其他模块的模块
define({
methods: function () {}
})
define(function () {
return {
m1: function () {},
m2: function () {}
}
})
// 依赖其他模块的模块
define(['./module-1', './module-2'], {
function (m1, m2) {}
})
// 语法糖
define(function (require) {
var m1 = require('./module-1'),
m2 = require('./module-2')
// ...
return function () {}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
- 加载模块
require(['m1', 'm2'], function (m1, m2) {
// 在加载完成 m1 和 m2 后,执行本匿名回调函数
m1.doSomething()
m2.doSomething1()
})
2
3
4
5
示例中,在加载 m1 和 m2 完成后,执行一个回调函数,该回调函数用于完成指定的任务。
CommonJS
CommonJS
主要是在服务端(Node.js
)运行的模块加载器,因为在服务端不存在像客户端一样在使用前需要先从网络下载 JS 文件,那么在 CommonJS
中所有引入的模块默认都是同步加载的。
常见使用即是 webpack
等构建工具中的使用。
// 引入 utils 模块
const someData = require('utils.js')
// 当前模块对模块外部的公有接口
module.exports = {
getSomething: function () {
// do something ...
}
}
2
3
4
5
6
7
8
9
AMD 和 CommonJS 使用场景区分
需要异步加载 JS 模块,使用 AMD
项目中引入了 npm ,则建议使用 CommonJs
构建工具
- webpack
拥有各项功能的全能型前端构建工具。它不仅仅局限于 JS 模块合并打包,而且还有打包样式表,外部资源,压缩代码等其他高级功能。
适用场景:常规前端项目构建打包。但不推荐 JS 库打包使用,因为过于繁重。
- rollup
极简的 JS 模块打包工具,相对于 webpack
的各项高级功能(全能手)来说,rollup
只专注于模块合并打包功能的实现。
在 JS 库的打包构建中,使用 rollup
打包而成的 bundle.js
大小远小于 webpack
打包的 bundle.js``。rollup
打包生成的 bundle.js
非常简洁。
适用场景:JS 库打包,如 Vue.js
库就是使用了 rollup
打包生成。