分享

前端模块化开发(AMD和CDM规范)

 昵称19107491 2014-08-27

前端模块化开发(AMD和CDM规范)

JS

2014-8-9  作者:前端工程师小V  浏览:34

标签: javascript javascript的依赖管理 前端模块化开发

1、AMD和CDM规范来历

前提:
CommonJS 原来叫 ServerJS,推出 Modules/1.0 规范后,在 Node.js 等环境下取得了很不错的实践。09年下半年这帮充满干劲的小伙子们想把 ServerJS 的成功经验进一步推广到浏览器端,于是将社区改名叫 CommonJS,同时激烈争论 Modules 的下一版规范。分歧和冲突由此诞生,逐步形成了三大流派:

1.Modules/1.x 流派。这个观点觉得 1.x 规范已经够用,只要移植到浏览器端就好。要做的是新增 Modules/Transport 规范,即在浏览器上运行前,先通过转换工具将模块转换为符合 Transport 规范的代码。主流代表是服务端的开发人员。现在值得关注的有两个实现:越来越火的 component 和走在前沿的 es6 module transpiler。

2.Modules/Async 流派。这个观点觉得浏览器有自身的特征,不应该直接用 Modules/1.x 规范。这个观点下的典型代表是 AMD 规范及其实现 RequireJS。

3.Modules/2.0 流派。这个观点觉得浏览器有自身的特征,不应该直接用 Modules/1.x 规范,但应该尽可能与 Modules/1.x 规范保持一致。这个观点下的典型代表是 BravoJS 和 FlyScript 的作者。BravoJS 作者对 CommonJS 的社区的贡献很大,这份 Modules/2.0-draft 规范花了很多心思。
FlyScript 的作者提出了 Modules/Wrappings 规范,这规范是 CMD 规范的前身。可惜的是 BravoJS 太学院派,FlyScript 后来做了自我阉割,将整个网站(flyscript.org)下线了。


AMD
异步模块定义(AMD)的编程接口提供了定义模块,及异步加载该模块的依赖的机制。它非常适合于使用于浏览器环境,浏览器的同步加载模块机制会带来性能,可用性,调试和跨域访问的问题。

本规范只定义了一个函数 "define",它是全局变量。函数的描述为:
   
define(id?, dependencies?, factory);

id
是个字符串,它指的是定义中模块的名字,这个参数是可选的。如果没有提供该参数,模块的名字应该默认为模块
加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。

dependencies
是个定义中模块所依赖模块的数组。依赖模块必须根据模块的工厂方法优先级执行,并且执行的结果应该按照依赖数组中的位置顺序以参数的形式传入(定义中模块的)工厂方法中。

依赖的模块名如果是相对的,应该解析为相对定义中模块。换句话来说,相对名解析为相对与模块的名字,并非相对于寻找该模块的名字的路径。

本规范定义了截然不同的三种特殊的依赖关键字。如果"require","exports", 或 "module"出现在依赖列表中,参数应该按照CommonJS模块规范自由变量去解析。

依赖参数是可选的,如果忽略此参数,它应该默认为["require", "exports", "module"]。然而,如果工厂方法的长度属性小于3,加载器会选择以函数的长度属性指定的参数个数调用工厂方法。


Factory
为模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。

如果工厂方法返回一个值(对象,函数,或任意强制类型转换为true的值),应该为设置为模块的输出值。

下面的例子显示了requirejs如何动态加载模块。
define(function ( require ) {
    var isReady = false, foobar;
 
    require(['foo', 'bar'], function (foo, bar) {
        isReady = true;
        foobar = foo() + bar();
    });
 
    return {
        isReady: isReady,
        foobar: foobar
    };
});
上面代码所定义的模块,内部加载了foo和bar两个模块,在没有加载完成前,isReady属性值为false,加载完成后就变成了true。因此,可以根据isReady属性的值,决定下一步的动作。

require.js
config方法,用来配置require.js运行参数。config方法接受一个对象作为参数。


paths
参数指定各个模块的位置。这个位置可以是同一个服务器上的相对位置,也可以是外部网址。可以为每个模块定义多个位置,如果第一个位置加载失败,则加载第二个位置,上面的示例就表示如果CDN加载失败,则加载服务器上的备用脚本。需要注意的是,指定本地文件路径时,可以省略文件最后的js后缀名。

baseUrl
参数指定本地模块位置的基准目录,即本地模块的路径是相对于哪个目录的。该属性通常由require.js加载时的data-main属性指定。

shim
有些库不是AMD兼容的,这时就需要指定shim属性的值。shim可以理解成“垫片”,用来帮助require.js加载非AMD规范的库。
require.config({
    paths: {
        "backbone": "vendor/backbone",
        "underscore": "vendor/underscore"
    },
    shim: {
        "backbone": {
            deps: [ "underscore" ],
            exports: "Backbone"
        },
        "underscore": {
            exports: "_"
        }
    }
});
上面代码中的backbone和underscore就是非AMD规范的库。shim指定它们的依赖关系(backbone依赖于underscore),以及输出符号(backbone为“Backbone”,underscore为“_”)。


CDM

CMD 模块定义规范

在 CMD 规范中,一个模块就是一个文件。代码的书写格式如下:
define(factory);

define 是一个全局函数,用来定义模块。
define(factory)
define 接受 factory 参数,factory 可以是一个函数,也可以是一个对象或字符串。

factory为对象、字符串时,表示模块的接口就是该对象、字符串。比如可以如下定义一个 JSON 数据模块:
define({ "foo": "bar" });

factory 为函数时,表示是模块的构造方法。执行该构造方法,可以得到模块向外提供的接口。factory 方法在执行时,默认会传入三个参数:require、exports 和 module:

define(function(require, exports, module) {
// 模块代码
});

require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口。

中间包括两个概念
相对标识  以 . 开头,只出现在模块环境中(define 的 factory 方法里面)。相对标识永远相对当前模块的 URI 来解析:

顶级标识  不以点(.)或斜线(/)开始, 会相对模块系统的基础路径(即 Sea.js 的 base 路径)来解析:

define(id?, deps?, factory)
define 也可以接受两个以上参数。
字符串 id 表示模块标识,
数组 deps 是模块依赖。
比如:

define('hello', ['jquery'], function(require, exports, module) {

  // 模块代码

});
id 和 deps 参数可以省略

require
require 是 factory 函数的第一个参数。
require.async
1.require.async 来进行条件加载。
2.require 是同步往下执行,require.async 则是异步回调执行。require.async 一般用来加载可延迟异步加载的模块。

require.resolve(id)
使用模块系统内部的路径解析机制来解析并返回模块路径。该函数不会加载模块,只返回解析后的绝对路径。

 

define(function(require, exports) {

  console.log(require.resolve('./b'));
  // ==> http:///path/to/b.js

});
这可以用来获取模块路径,一般用在插件环境或需动态拼接模块路径的场景下。

exports
exports 是一个对象,用来向外提供模块接口。

除了给 exports 对象增加成员,还可以使用 return 直接向外提供接口。

module 
module是一个对象,上面存储了与当前模块相关联的一些属性和方法。

module.id
String
模块的唯一标识。

define('id', [], function(require, exports, module) {

  // 模块代码

});
上面代码中,define 的第一个参数就是模块标识。

module.uri
String
根据模块系统的路径解析规则得到的模块绝对路径。

define(function(require, exports, module) {

  console.log(module.uri);
  // ==> http:///path/to/this/file.js

});
一般情况下(没有在 define 中手写 id 参数时),module.id 的值就是 module.uri,两者完全相同。

module.exports
Object
当前模块对外提供的接口。

exports 参数是 module.exports 对象的一个引用。只通过 exports 参数来提供接口,有时无法满足开发者的所有需求。
比如当模块的接口是某个类的实例时,需要通过 module.exports 来实现:

define(function(require, exports, module) {

  // exports 是 module.exports 的一个引用
  console.log(module.exports === exports); // true

  // 重新给 module.exports 赋值
  module.exports = new SomeClass();

  // exports 不再等于 module.exports
  console.log(module.exports === exports); // false

});
注意:对 module.exports 的赋值需要同步执行,不能放在回调函数里。

 

相关参考

https://github.com/seajs/seajs/issues/242  CMD 模块定义规范
https://github.com/seajs/seajs/issues/588  前端模块化开发那点历史
https://github.com/seajs/seajs/issues/258   模块标识
https://github.com/amdjs/amdjs-api/wiki/AMD-(中文版)   AMD
http://javascript./tool/requirejs.html  


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多