From:http://snandy.iteye.com/blog/1450665
博客分类: Modular JS
因为 AMD 和CommonJS的糅合,所以先看看 CommonJS 和 AMD 是什么?
模块:简单理解就是JS的对象化,比如可写个event事件模块接口。模块返回一个匿名函数,有的返回给一个变量,有的暴露自己,如jQuery暴露自己给window对象。又如SeaJS,它一开始就将接口公开了。this.seajs = { _seajs: this.seajs };/*Base namespace for the framework. */
如果我们运用了“模块化”的思想来开发,但如果没有统一的语法格式来写模块且各种各样的风格来写模块而使代码乱套。
这时一些组织出现了,最具代表的如CommonJS ,AMD 。此外ECMAScript 也开始着手模块的标准化写法。
无论它们提供什么样的写法,我们需要的仅仅是:
- 将一些零散代码封装成一个有用的单元(encapsulate)
- 导出模块的接口API(exports)
- 方便友好引用其它模块(dependency)
CommonJS:
在服务器端运行的JS有NodeJS,他遵循了一个称为CommonJS的规范。CommonJS其中就有对写模块的标准化。当然模块化只是其中的一部分而已。
模块化方面它实现了Modules/1.0(已经更新到1.1.1 )。以下是node中是写模块的一个示例。
math.js 文件
- exports.add = function() {
- var sum=0, i=0, args=arguments, l=args.length; while(i{ sum += args[i++]; }
- return sum;
- };
increment.js
- var add = require('math').add; //math.js
- exports.increment = function(val) { return add(val, 1); };
main.js
该文件为入口文件
- var inc = require('increment').increment; //increment.js
- var a = 1;
- inc(a);
以上写了三个math、increment、main模块:
- math提供了add方法来实现数字相加。
- increment模块依赖于math模块,它提供increment方法实现相加。
- main获取到increment方法,执行相加操作。
我们看到
- node要求一个js文件对应一个模块.可把该js文件想象成一个匿名函数,所有代码都在匿名函数中,其它模块不可访问除exports外的私有变量
- 使用exports导出API.
- 使用require加载其它模块.
CommonJS module基本要求如下
- 标示符 require为一个函数,require(string) , 参数string须遵守Module Identifiers 的6点规定.
- require方法返回指定的模块API.
- 如果存在依赖的其它模块,那么依次加载.
- require不能返回,则抛出异常.
- 仅能使用标示符exports导出API.
Modules/1.1较1.0仅增加了标示符module,require函数增加了main和paths属性。而仔细比对1.1与1.1.1后发现除了格式调整了下几乎没有变化。
Modules/Wrappings
服务器端可以很容易的实现同步或异步请求模块,浏览器端则问题多多。
- var event = require("event");
- event.bind(el, 'click', function() {
-
- });
这段代码中require如果是异步执行的,则event.bind的执行有可能会出错。反之同步的require可以使用 XHR 实现同步载入模块JS文件。但XHR的缺点是不能跨域,这点让人很难接受,因为有些场景需要模块部署在不同的服务器。
那只能通过script tag来实现模块加载了, 但script tag默认就是异步 的,要实现NodeJS的一模一样风格(Modules)问题多多,几乎是不可能。
这时,又一模块格式出现了:Modules/Wrappings (还有AMD ),顾名思义包裹的模块。该规范约定如下
- 定义模块用module变量,它有一个方法declare
- declare接受一个函数类型的参数,如称为factory [e.g. module.declare(factory function); ]
- factory有三个参数分别为require、exports、module [e.g. function(require, exports, module)]
- factory使用返回值和exports导出API
- factory如果是对象类型,则将该对象作为模块输出 [e.g. module.declare({ foo: "bar"}); ]
描述有拗口,代码却很简单,使用了一个function包裹模块(NodeJS模块则无需包裹)。
基本的模块定义
- module.declare(function(require, exports, module){ exports.foo = "bar"; });
使用返回值的模块定义
- module.declare(function(require){ return { foo:"bar"}; });
直接使用对象作为模块
- module.declare({foo:"bar"});
Modules/Wrappings 的出现使得浏览器中实现它变得可能,包裹的函数作为回调, 即使用script tag作为模块加载器,script完全下载后去回调,回调中进行模块定义。
好了,截止目前我们已经看到了两种风格的模块定义:Modules 和 Modules/Wrappings 。
CommonJS Modules有1.0、1.1、1.1.1三个版本。
NodeJS、SproutCore 实现了 Modules 1.0 。
SeaJS 、AvocadoDB、CouchDB等实现了Modules 1.1.1 。
SeaJS、FlyScript 实现了Modules/Wrappings。
注:
1. SeaJS未实现全部的 Modules 1.1.1。如require函数的main, paths属性没有.但SeaJS给require添加了async、resolve、load、constructor;
2. SeaJS没有使用 Modules/Wrappings 中的module.declare定义模块,而是使用define函数.
AMD:浏览器中的模块规范
前面提到,为实现与NodeJS相同方式的模块写法,大牛们做了很多努力。
但由于浏览器环境不同于服务器端,它的模块有一个HTTP请求过程。这个请求过程多数使用script tag,script tag默认的异步性导致很难实现与NodeJS一模一样的模块格式。
Modules/Wrappings 使得实现变为现实。虽然和NodeJS的模块写法不完全一致,但也有很多相似之处,使得熟悉NodeJS的程序员有一些亲切感。但NodeJS终究是服务器端的JS,没有必要把这些条条框框放到浏览器JS环境中。这时AMD诞生了(异步模块定义)。从名称上看便知它是适合script tag的,也可以说AMD是专门为浏览器中JS环境设计的规范。它吸取了CommonJS的一些优点,但又不照搬它的格式。开始AMD作为CommonJS的transport format 存在,因无法与CommonJS开发者达成一致而独立存在。它有独立的wiki 和讨论组 。
AMD设计出一个简洁的写模块API:define 。
define([id, ] [dependencies, ] factory);
其中:
- id: 模块标识[可以省略]; [id遵循CommonJS Module Identifiers]
- dependencies: 所依赖的模块[可以省略]; [dependencies元素的顺序和factory参数一一对应]
- factory: 模块的实现,或者一个JavaScript对象。
以下是使用AMD模式开发的简单三层结构(
基础库 /UI层 /应用层)(以下
演示了define的定义三种用法):
base.js
定义无依赖的模块
- define(function() {
- return {
- mix: function(source, target) { ... }
- };
- });
ui.js
定义有依赖的模块
- define(['base'], function(base) {
- return {
- show: function() {
-
- }
- }
- });
page.js
定义有依赖的模块
- define(['data', 'ui'], function(data, ui) {
-
- });
data.js
定义数据对象模块
- define({
- users: [],
- members: []
- });
细心的会发现,还有一种没有出现,即具名模块
4,具名模块
- define('index', ['data','base'], function(data, base) {
-
- });
具名模块多数时候是不推荐的,一般打包工具合并多个模块到一个js文件中时会使用。
前面提到dependencies元素的顺序和factory一一对应,其实不太严谨。AMD开始为摆脱CommonJS的束缚,开创性的提出了自己的模块风格。但后来又做了妥协,兼容了 CommonJS Modules/Wrappings 。即又可以这样写
5,包装模块
- define(function(require, exports, module) {
- var base = require('base');
- exports.show = function() {
-
- }
- });
不考虑多了一层函数外,格式和NodeJS还比较象的。使用require获取依赖模块,使用exports导出API。
除了define外,AMD还保留一个关键字require。
require 作为规范保留的全局标识符,可以实现为
module loader。也可以不实现。
目前,实现AMD的库有 RequireJS 、 curl 、 Dojo 、 bdLoad 、 JSLocalnet 、 Nodules 等。
也有很多库支持AMD规范,即将自己作为一个模块存在,如 MooTools 、 jQuery 、 qwery 、 bonzo 甚至还有 firebug 。
UMD: AMD 和CommonJS的糅合
前面花了很长的篇幅介绍了两大类模块规范,CommonJS(Modules / Modules/Wrappings) 及 AMD。
Modules/Wrappings是出于对NodeJS模块格式的偏好而包装下使其在浏览器中得以实现, 而且它的格式通过某些工具(如r.js)也能运行在NodeJS中。事实上,这两种格式同时有效且都被广泛使用。
AMD以浏览器为第一(browser-first)的原则发展,选择异步加载模块。它的模块支持对象(objects)、函数(functions)、构造器(constructors)、字符串(strings)、JSON等各种类型的模块。因此在浏览器中它非常灵活。
CommonJS以服务器端为第一(server-first)的原则发展,选择同步加载模块。它的模块是无需包装的(unwrapped modules)且贴近于ES.next/Harmony的模块格式。但它仅支持对象类型(objects)模块。
这迫使一些人又想出另一个更通用格式 UMD(Universal Module Definition)。希望提供一个前后端跨平台的解决方案。
UMD的实现很简单,先判断是否支持NodeJS模块格式(exports是否存在),存在则使用NodeJS模块格式。
再判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。前两个都不存在,则将模块公开的全局(window或global)。
下面是一个示例
eventUtil.js
- (function (root, factory) {
- if (typeof exports === 'object') {
- module.exports = factory();
-
- } else if (typeof define === 'function' && define.amd) {
- define(factory);
-
- } else {
- root.eventUtil = factory();
- }
- })(this, function() {
-
- return {
- addEvent: function(el, type, handle) {
-
- },
- removeEvent: function(el, type, handle) {
-
- },
- };
- });
虽然UMD八字还没有一撇,有些开源库却开始支持UMD了, 如大名鼎鼎的《JavaScript设计模式》作者Dustin Diaz开发的qwery。 代码如下
二、ECMAScript:未来的JS模块化
ECMAScript的下一个版本Harmony已经考虑到了模块化的需求。
使用module关键字来定义一个模块
- module math {
- export function sum(x, y) {
- return x + y;
- }
- export var pi = 3.141593;
- }
使用import关键字来加载外部模块
-
- import {sum, pi} from math;
- alert("2π = " + sum(pi, pi));
引入所有API
-
- import * from math;
- alert("2π = " + sum(pi, pi));
使用另一个引用作为别名
-
- module M = math;
-
-
- alert("2π = " + M.sum(M.pi, M.pi));
局部重命名
- import { draw: drawShape } from shape;
- import { draw: drawGun } from cowboy;
嵌套模块
- module widgets {
- export module button { ... }
- export module alert { ... }
- export module textarea { ... }
- ...
- }
-
- import { messageBox, confirmDialog } from widgets.alert;
- ...
从服务器上请求的模块