为什么要有模块化

早期的网站一般只是用来展示一些静态的内容,只有 HTML 和 CSS。后来为了增强用户体验,使用户可以与网页做一些简单交互,NetScape 创建了 JavaScript 这门脚本语言,使用它可以动态修改 html。

后来,Google 发明了 Ajax 技术,这大大促进了 JavaScript 在编写网页时所占的比重,人们清楚的看到了 JavaScript 的威力,于是,JavaScript 开始在 WEB 前端中发挥越来越重要的作用,前端程序员需要维护的 JavaScript 代码也越来越多,越来越复杂。

现在,一个项目的前端代码往往是由多个人协作开发的。项目组长搭好整体结构,写好一些通用的、全局的内容,然后将需要实现的功能分配给各个组员,组员们写好代码后,项目组长再统一将 JS 代码引入到 HTML 中。这样就会出现一个问题:因为所有的 JS 代码共用一个作用域,所以很容易导致命名冲突(两个组员可能会使用同一个变量名)。

所以,“前端模块化” 的需求就诞生了。

ES5 模块化

ES5 中还没有专门用来模块化的语法,所以得自己想办法实现模块化。

利用函数作用域防止命名冲突

下面是一个 JavaScript 文件的简单例子:

  1. // a.js
  2. ;(function() { // 前面的分号是为了防止其他文件的干扰
  3. var a = 1;
  4. var b = 2;
  5. function sum(num1, num2) {
  6. return num1 + num2;
  7. }
  8. var flag = true;
  9. if(flag) {
  10. console.log(sum(a, b));
  11. }
  12. })() // 在定义函数的同时调用函数

将所有操作都定义在匿名函数内部,然后调用该函数,这样,所有的变量都定义在函数的局部作用域中,对全局作用域不会有影响。

这样做解决了命名冲突问题,但如果想在其他 JS 文件中调用该文件定义的变量或函数就不行了,所以还需要再加工一下才能真正实现模块化。

ES5 实现模块化的方法

将上面的例子加工一下:

  1. // a.js
  2. var moduleA = (function() {
  3. var obj = {} // 定义一个空对象
  4. var a = 1;
  5. var b = 2;
  6. function sum(num1, num2) {
  7. return num1 + num2;
  8. }
  9. var flag = true;
  10. if(flag) {
  11. console.log(sum(a, b));
  12. }
  13. obj.sum = sum; // 将需要导出的变量全部赋值给 obj 的属性
  14. obj.flag = flag;
  15. return obj; // 返回 obj
  16. })()

用一个变量(moduleA)接收该函数的返回值,这样就可以在全局作用域中通过 moduleA.xxx 的方式拿到我们需要的变量或函数了。

以上是 ES5 中模块化的最基本的封装,事实上关于模块化还有很多高级的话题。

ES6 模块化规范

模块化的核心:导入导出。

ES6 中原生支持了模块的导入导出。

ES6 中模块的导入导出

举例说明:

  1. 首先,在引入外部 JS 文件时,需要给 script 标签加上一个 type 属性:这样,每个 JS 文件都会被当做一个模块,不同模块中的相同变量名不会再发生冲突。

    1. <!-- index.html -->
    2. <script src="a.js" type="module"></script>
    3. <script src="b.js" type="module"></script>
  2. 导出需要共享的变量或函数: ```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; }

  1. 3. 在需要的地方导入并使用:
  2. ```javascript
  3. // b.js
  4. import {sum, flag} from "./a.js"; // import 后是对象的解包语法;from 后跟相对路径
  5. console.log(sum(1, 2));

export default

  1. // a.js
  2. let a = 1;
  3. let b = 2;
  4. function sum(num1, num2) {
  5. return num1 + num2;
  6. }
  7. let flag = true;
  8. export default sum;
  1. // b.js
  2. import add from "./a.js";

使用这种方式导出时:

  1. 导入不再需要写大括号。
  2. 名字可以任意起,如上例中导出的是 sum,但导入时重命名为 add。
  3. 一个文件中只能有一个 export default

其他模块化规范

现在比较常见的模块化规范还有 CommonJS、AMD、CMD。注意:JavaScript 不原生支持这些模块化规范,它们只能在特定的场景中使用。

CommonJS

NodeJS 中的模块化就是按照 CommonJS 规范实现的。

导出:

  1. // a.js
  2. let a = 1;
  3. let b = 2;
  4. function sum(num1, num2) {
  5. return num1 + num2;
  6. }
  7. let flag = true;
  8. module.exports = {
  9. sum,
  10. flag
  11. }

导入:

  1. // b.js
  2. let moduleA = require("./a.js");
  3. console.log(moduleA.sum(1, 2));
  4. // 也可以在导入时直接解包:
  5. let {sum, flag} = require("./a.js");
  6. console.log(sum(1, 2));