为什么要有模块化
早期的网站一般只是用来展示一些静态的内容,只有 HTML 和 CSS。后来为了增强用户体验,使用户可以与网页做一些简单交互,NetScape 创建了 JavaScript 这门脚本语言,使用它可以动态修改 html。
后来,Google 发明了 Ajax 技术,这大大促进了 JavaScript 在编写网页时所占的比重,人们清楚的看到了 JavaScript 的威力,于是,JavaScript 开始在 WEB 前端中发挥越来越重要的作用,前端程序员需要维护的 JavaScript 代码也越来越多,越来越复杂。
现在,一个项目的前端代码往往是由多个人协作开发的。项目组长搭好整体结构,写好一些通用的、全局的内容,然后将需要实现的功能分配给各个组员,组员们写好代码后,项目组长再统一将 JS 代码引入到 HTML 中。这样就会出现一个问题:因为所有的 JS 代码共用一个作用域,所以很容易导致命名冲突(两个组员可能会使用同一个变量名)。
所以,“前端模块化” 的需求就诞生了。
ES5 模块化
ES5 中还没有专门用来模块化的语法,所以得自己想办法实现模块化。
利用函数作用域防止命名冲突
下面是一个 JavaScript 文件的简单例子:
1 | // a.js |
将所有操作都定义在匿名函数内部,然后调用该函数,这样,所有的变量都定义在函数的局部作用域中,对全局作用域不会有影响。
这样做解决了命名冲突问题,但如果想在其他 JS 文件中调用该文件定义的变量或函数就不行了,所以还需要再加工一下才能真正实现模块化。
ES5 实现模块化的方法
将上面的例子加工一下:
1 | // a.js |
用一个变量(moduleA)接收该函数的返回值,这样就可以在全局作用域中通过 moduleA.xxx
的方式拿到我们需要的变量或函数了。
以上是 ES5 中模块化的最基本的封装,事实上关于模块化还有很多高级的话题。
ES6 模块化规范
模块化的核心:导入导出。
ES6 中原生支持了模块的导入导出。
ES6 中模块的导入导出
举例说明:
首先,在引入外部 JS 文件时,需要给 script 标签加上一个 type 属性:
1
2
3<!-- index.html -->
<script src="a.js" type="module"></script>
<script src="b.js" type="module"></script>这样,每个 JS 文件都会被当做一个模块,不同模块中的相同变量名不会再发生冲突。
导出需要共享的变量或函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// a.js
let a = 1;
let b = 2;
function sum(num1, num2) {
return num1 + num2;
}
let flag = true;
// 导出方式一:
export {
sum, flag
}
// 导出方式二:
export sum;
export flag;
export let s = "hello world"; // 也可以在定义的同时导出
export function add(num1, num2) {
return num1 + num2;
}在需要的地方导入并使用:
1
2
3
4// b.js
import {sum, flag} from "./a.js"; // import 后是对象的解包语法;from 后跟相对路径
console.log(sum(1, 2));
export default
1 | // a.js |
1 | // b.js |
使用这种方式导出时:
- 导入不再需要写大括号。
- 名字可以任意起,如上例中导出的是 sum,但导入时重命名为 add。
- 一个文件中只能有一个
export default
。
其他模块化规范
现在比较常见的模块化规范还有 CommonJS、AMD、CMD。注意:JavaScript 不原生支持这些模块化规范,它们只能在特定的场景中使用。
CommonJS
NodeJS 中的模块化就是按照 CommonJS 规范实现的。
导出:
1 | // a.js |
导入:
1 | // b.js |