JavaScript模块的前世今生

jopen 10年前

如今JavaScript模块化编程的概念已经普及开来,一提起模块化,大家想到的可能是AMD,CMD,requirejs或seajs。其实还有很多其他的概念。本文将会陈述下JavaScript模块的前世今生。

众所周知,JavaScript由于历史的原因并没有模块的概念,自从ajax带来了web2.0概念后,js代码已经和以前大不相同了,2009年HTML5兴起后,前端代码的行数已经呈现井喷式发展,随着代码量的增加,模块的缺失的缺点日益凸显,Javascript社区做了很多探索。

模块的定义

模块并非js语言独创,显然是借鉴其他语言的,下面是百度百科对模块的定义:

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

从中提炼出几个关键字就是,独立,集合,完成一定功能。

上面的提炼,再从其他语言的实现中借鉴下,总结起来,我们期待的模块有如下特性:

  • 独立性——能够独立完成一个功能,不受外部环境的影像
  • 完整性——完成一个特定功能
  • 集合性——一组语句的集合
  • 依赖性——可以依赖已经存在的模块
  • 被依赖——可以被其他模块依赖

其实我们想要的就是一个独立的模块,并能引用依赖,及被依赖。

C语言的库和头文件(include),java的包(import)。这在其他语言中都是原生支持的特性,在js中却是没有的。

原始写法

如果仅从定义入手,那么一个函数即可成为一个模块(独立,集合,完成一个功能),那我们就先从最原始的探索开始,也许不经意间,我们早已在使用模块了。

//最简单的函数,可以称作一个模块  function add(x, y) {   return x + y;  }

稍微了解点javascript基础的人都知道js中能创建作用域的就是函数(ES6之前),总结下社区的探索,对模块的模拟大概如下:

(function (mod, $, _) {   mod.add = ***;   mod.sub = ***;  }((window.mod = window.mod || {}), jQuery, Underscore));

上面的mod模块不会重复定义,可自由定义依赖。

99%的人思想会止步于此,但这种实现其实并不完美,仍然需要手动维护依赖的顺序。典型的场景就是上面的jquery必须先于我们的代码引入,不然会报引用错误,这显然不是我们想要的。

我在写Painter的时候,曾经手动维护几十个script之间的先后顺序,这种感觉很虐心,最后想加个新script很容易报错。下面介绍的

YUI

前段时间雅虎宣布YUI不再更新了,很是伤感,最早接触模块的概念,当属YUI了,当然不是YUI2了。

YUI3经过全新设计,使用了沙箱模式 + 命名空间的方式,并有了模块的概念。

例如在YUI3中想使用一个模块,需要如下这样:

//使用node模块,node模块会作为参数传入  YUI().use('node', function (Y) {   ///***  }

YUI的模块化已经做的很好了,但对于仅想使用模块的人,要引入YUI确实有点太重了。

CMD(Common Module Definition)

说道CMD就不能不提commonjs,提到commonjs就不能不提node

CMD规范参照commonjs中的方式,定义模块的方式如下:

define(function(require, exports, module) {      // The module code goes here  });

一个文件就是一个模块,文件名就是模块的名字,使用模块的方法也和commonjs中一致,只需require就好了,模块名字可省略后缀。

//使用event.js模块  var ec = require('event');

CMD的典型实现就是seajs,应用的很广泛。

AMD(Asynchronous Module Definition)

[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD-(%E4%B8%AD%E6%96%87%E7%89%88)是异步模块定义,特别适合在浏览器端使用,其规范和CMD是很像的,AMD规范中定义模块的方式如下:

define(id?, dependencies?, factory);

同CMD一样,一个文件即一个模块,模块的使用方法如下:

define(["beta"], function (beta) {   bata.***//调用模块  });

AMD主张依赖注入,这点和CMD不同(以来查找)。

AMD也支持已CMD的方式来使用依赖。

AMD的典型实现有requireJSmodJSlodJS

KMD

KMD是kissy中提出来的,是kissy自己的一套模块化方案,具体我也不是很清楚,感兴趣的同学可自行搜索相关资料。

有一次同事@eric曦尧无意说起,KMD的意思是 kill amd and cmd,当时觉得好高打上的名字(/ □ \)。

ES6

ES6带来了语言层面的模块化支持,规范方面见这里,文档方面见这里

我们现在期待的就是ES6规范快点尘埃落定(据说今年夏天),现在还处于草案状态,还有浏览器厂商们的大力支持,还有就是在国内尽快普及开来。

UMD

UMD的全称是Universal Module Definition。和它名字的意思一样,这种规范基本上可以在任何一个模块环境中工作。

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

(function (root, factory) {      var Data = factory(root);      if ( typeof define === 'function' && define.amd) {          // AMD          define('data', function() {              return Data;          });      } else if ( typeof exports === 'object') {          // Node.js          module.exports = Data;      } else {          // Browser globals          var _Data = root.Data;                    Data.noConflict = function () {              if (root.Data === Data) {                  root.Data = _Data;              }                            return Data;          };          root.Data = Data;      }  }(this, function (root) {   var Data = ...   //自己的代码   return Data;  }));

这是出自data.js中的一部分代码,其原理就是做个判断,不同的环境进行不同的处理。

我已将UMD应用到自己的项目中,瞬间感觉高大上了不少:-)。

总结

比较成气候的模块化方案,当属AMD和CMD,网上关于二者比较的文章甚多,很难评价谁好谁坏,当下开来AMD的使用范围似乎更广些,而CMD的本土化方面做的更好。

这些模块化的探索,使前端工程化成为了可能,可以说没有模块,工程化更无从弹起,本文总结了大家在模块化方面的一些探索,下一篇文章将重点介绍下lodJS(一款基于AMD的模块加载器)的实践和原理。

参考资料

来自:http://yanhaijing.com/js/2015/03/28/js-module/