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