Skip to content

第 26 题:介绍模块化发展历程

提示

JavaScript 发展初期就是为了实现简单的页面交互逻辑,寥寥数语即可;如今 CPU、浏览器性能得到了极大的提升,很多页面逻辑迁移到了客户端(表单验证等),随着 web2.0 时代的到来,Ajax 技术得到广泛应用,jQuery 等前端库层出不穷,前端代码日益膨胀,此时在 JS 方面就会考虑使用模块化规范去管理。 本文内容主要有理解模块化,为什么要模块化,模块化的优缺点以及模块化规范,并且介绍下开发中最流行的 CommonJS, AMD, ES6CMD规范。

模块化的理解

  1. 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起
  2. 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信
  3. 模块化主要是用来抽离公共代码,隔离作用域,避免变量冲突等。

CommonJS

概述

信息

Node 应用由模块组成,采用 CommonJS 模块规范。每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。

特点

  1. 所有代码都运行在模块作用域,不会污染全局作用域。
  2. 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  3. 模块加载的顺序,按照其在代码中出现的顺序。

基本语法

  1. 暴露模块module.exports = valueexports.xxx = value
  2. 引入模块require(xxx),如果是第三方模块,xxx 为模块名;如果是自定义模块,xxx 为模块文件路径
js
// example.js
var x = 5;
var addX = function (value) {
  return value + x;
};
module.exports.x = x;
module.exports.addX = addX;

上面代码通过 module.exports 输出变量 x 和函数 addX

js
var example = require("./example.js"); //如果参数字符串以“./”开头,则表示加载的是一个位于相对路径
console.log(example.x); // 5
console.log(example.addX(1)); // 6

警告

require 命令用于加载模块文件。require 命令的基本功能是,读入并执行一个 JavaScript 文件,然后返回该模块的 exports 对象。如果没有发现指定模块,会报错。

AMD

信息

CommonJS 规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD 规范则是非同步加载模块,允许指定回调函数。由于 Node.js 主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以 CommonJS 规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用 AMD 规范。此外 AMD 规范比 CommonJS 规范在浏览器端实现要来着早。

基本语法

  1. 定义暴露模块:
js
//定义没有依赖的模块
define(function () {
  return 模块;
});
js
//定义有依赖的模块
define(["module1", "module2"], function (m1, m2) {
  return 模块;
});
  1. 引入使用模块:
js
require(["module1", "module2"], function (m1, m2) {
  使用m1 / m2;
});

CMD

信息

CMD 规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。CMD 规范整合了 CommonJSAMD 规范的特点。在 Sea.js 中,所有 JavaScript 模块都遵循 CMD 模块定义规范。

基本语法

  1. 定义暴露模块:
js
//定义没有依赖的模块
define(function (require, exports, module) {
  exports.xxx = value;
  module.exports = value;
});
js
//定义有依赖的模块
define(function (require, exports, module) {
  //引入依赖模块(同步)
  var module2 = require("./module2");
  //引入依赖模块(异步)
  require.async("./module3", function (m3) {});
  //暴露模块
  exports.xxx = value;
});
  1. 引入使用模块:
js
define(function (require) {
  var m1 = require("./module1");
  var m4 = require("./module4");
  m1.show();
  m4.show();
});

ES6 模块化

信息

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJSAMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。

模块化语法

  1. export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能。
js
/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {
  return a + b;
};
export { basicNum, add };
/** 引用模块 **/
import { basicNum, add } from "./math";
function test(ele) {
  ele.textContent = add(99 + basicNum);
}

如上例所示,使用 import 命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到 export default命令,为模块指定默认输出。

js
// export-default.js
export default function () {
  console.log("foo");
}
js
// import-default.js
import customName from "./export-default";
customName(); // 'foo'

参考资料:前端模块化思维导图