阅读 708

JavaScript 模块的发展史

我们都知道 JavaScript 中并没有模块的概念,一开始 JavaScript 的出现只是作为简单脚本语言来实现简单的页面逻辑,而随着互联网的发展和 web 2.0 时代的到来,前端代码呈现井喷式发展,随着代码量的增加,模块缺失的问题日益凸显,而同时 JavaScript 社区也做了很多探索。

那么什么是模块呢?

模块,是指能够单独命名并独立地完成一定功能的程序语句的集合(即程序代码和数据结构的集合体)。

所以,模块的核心就是需要完成特定的功能,并且其很重要的一点就是需要解决引用依赖以及被依赖的问题。

函数

从定义上来说,其实函数也可以被当作是一个模块。

在 myModule.js 中新增如下函数

function add(x, y) {
  return x + y;
}
function minus(x, y) {
  return x - y;
}
复制代码

通过 script 标签引入 myModule.js 后可直接使用里面的函数

<script src="myModule.js"></script>
<script>
  console.log(add(1, 2)) // 3
  console.log(minus(2, 1)) // 1
</script>
复制代码

在 myModule.js 中,每一个函数都可以被认为是一个模块。通过函数方式定义模块主要有两个缺陷:一是会污染全局变量,无法保证各模块间的变量名不冲突;二是需要手动维护依赖的顺序,如果 myModule.js 中的模块需要依赖其它模块(如 jQuery),则该模块(jQuery)需要在 myModule.js 之前引用。

CommonJS

自 2009 年 NodeJS 诞生后,JavaScript 模块化编程正式进入人们的视野,而 NodeJS 的模块化系统,便是参照 CommonJS 规范实现的。

在 CommonJS 规范中,一个文件就是一个模块,每个模块都有各自的作用域,即在一个模块中的变量、函数是私有的,外部无法访问。

在模块内部,module 变量对象代表当前模块,其 exports 属性也是一个对象,代表对外的接口,所以我们将需要对外暴露的内容放进 module.exprots 对象即可;而引用模块则使用 require 函数。

使用 CommonJS 规范来定义 myModule 模块,在 myModule.js 中写入如下代码

function add(x, y) {
  return x + y;
}
function minus(x, y) {
  return x - y;
}
module.exports = {
  add: add,
  minus: minus
}
复制代码

在 main.js 中引用 myModule 模块

var myModule = require('./myModule.js');
console.log(myModule.add(1,2)); // 3
console.log(myModule.minus(2,1)); // 1
复制代码

但是,由于在 CommonJS 规范中,require 加载模块的方式是同步加载,使得其不适合在浏览器端使用。在服务器端同步加载模块,等待时间取决于硬盘的读取时间,而在浏览器端同步加载模块,等待时间取决于网速快慢,这使得在等待加载模块的过程中,浏览器会处于假死的状态。

AMD(Asynchronous Module Definition)

AMD 即异步模块定义,是 RequireJS 在推广过程中对模块定义的规范化产出。同样的,其规定一个文件就是一个模块,文件名即模块名。

AMD 使用 define 函数定义模块;使用 require 函数引用模块。

define 函数使用方式如下

define(id?, dependencies?, factory);
复制代码

定义 myModule.js 模块

define(['depenModule'], function (depenModule) {
  //do something
});
复制代码

require 接收两个参数,第一个参数为所依赖的模块标识数组;第二个参数为依赖模块加载完成后的回调函数。

在 main.js 中引用 myModule 模块

require(['myModule'], function (myModule){
  //do something
});
复制代码

AMD 推崇依赖前置,需要先异步加载其所需的依赖模块后才会执行相应回调函数中的代码。

CMD(Common Module Definition)

CMD 即公共模块定义,是 SeaJS 在推广过程中对模块定义的规范化产出。同样的,其规定一个文件就是一个模块,文件名即模块名。

其使用 define 函数定义模块;使用 require 函数引用模块。

define 函数使用方式如下

define(factory)
复制代码

定义 myModule.js 模块

define(function(require, exports, module) {
  //do something
});
复制代码

在 main.js 中引用 myModule 模块

var myModule = require('./myModule.js');
//do something
复制代码

CMD 推崇依赖就近,在书写代码的过程中再根据其所需要的依赖 require 进来。

AMD 与 CMD 对于依赖的模块都是异步加载。 其最大的区别是对依赖模块的执行时机处理不同。

AMD 依赖前置,浏览器会立即加载其依赖模块;而 CMD 是依赖就近,需要将模块转为字符串解析才能确认其依赖模块并加载,这是一种牺牲性能来带来开发的便利性的做法。

UMD(Universal Module Definition)

UMD 即通用模块定义,是一种基本上可以在任何一个模块环境中工作的规范。

一段典型的 UMD 代码如下所示

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : // CMD
    typeof define === 'function' && define.amd ? define(['exports'], factory) : // AMD
      (factory((global['module'] = {}))); // Browser globals
}(this, (function (exports) {
  'use strict';

  exports.x = x;
  Object.defineProperty(exports, '__esModule', { value: true });
})));
复制代码

其原理是通过对不同的环境的判断做相应的处理。

ES6 模块

在 ES6 中,JavaScript 终于有了自己真正模块的概念。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代之前的规范,成为浏览器和服务器通用的模块解决方案。

在 ES6 模块系统中,通过 export 和 export default 命令规定模块的对外接口,import 命令输入模块提供的功能。

myModule.js 中

function add(x, y) {
  return x + y;
}
function minus(x, y) {
  return x - y;
}

export {
  add,
  minus
}
复制代码

main.js 中

import { add, minus } from './myModules.js';

console.log(add(1, 2)) // 3
console.log(minus(2, 1)) // 1
复制代码

对于 ES6 的模块在此不多做介绍,参考文档见阮一峰大神的《 ECMAScript 6 入门 》

写在最后

在 JavaScript 模块化的探索道路上,出现了很多优秀的规范与框架,除了上述具有代表性的规范外,还有像 YUI、KMD 等其它优秀的规范框架。

虽然说 JavaScript 的模块化发展史(更确切的应该是 JavaScript 发展史)一路充满艰辛坎坷,但其实我们可以看到它正走在正确的道路上,越来越好,衷心希望未来 JavaScript 能够越来越强大,能够让我们在未来遇见更多的可能。

参考资料

JavaScript模块的前世今生

公众号不定时分享个人在前端方面的学习经验,欢迎关注。

关注下面的标签,发现更多相似文章
评论