小谈AMD与CMD

小谈AMD与CMD

命名冲突和文件依赖,是前端开发过程中的两个经典问题。人们尝试通过模块化开发方法和思维来解决这些问题。

Sea.js与CMD模块化规范

简介

Sea.js 是一个适用于 Web 浏览器端的模块加载器。遵循CMD模块化标准。

定义模块

define(function(require, exports, module){

})

引入模块

var foo = require('./foo.js') // .js可以被省略

注意require的参数,即路径必须是字符串直接量,不得是任何形式的表达式。

异步加载模块

require对模块进行同步加载,如果想要异步加载模块,可以使用

require.async('./foo.js', function(foo){
// do something after the module is loaded
foo.doSomething();
})
// 或
require.async(['./foo.js','./bar.js'], function(foo, bar){

})
路径

路径有相对、顶级与普通之分。相对路径相对于当前路径的uri进行解析,特征是以./或../开头。顶级路径前没有/或.,相对于模块系统的基础路径(即 Sea.js 的 base 路径)来解析。也可以设置普通路径,即绝对路径或根路径(/)。此外sea.use()中的路径始终是普通路径。此外,Sea.js会为每个没有后缀名的文件自动加上js后缀,如果不想这么做,要么为文件加上后缀名,要么在文件名后面加#。

导出模块

exports.foo = foo;
//或
module.exports = bar;
// 或
return {
    foo:foo,
    bar:bar
}

注意:导出语句必须同步执行,不能放在比如setTimeout等函数的回调中。

配置

seajs.config({
  // 别名配置
  alias: {
    'es5-safe': 'gallery/es5-safe/0.9.3/es5-safe',
    'json': 'gallery/json/1.0.2/json',
    'jquery': 'jquery/jquery/1.10.1/jquery'
  },
  // 路径配置,paths 配置可以结合 alias 配置一起使用,让模块引用非常方便。
  paths: {
    'gallery': 'https://a.alipayobjects.com/gallery'
  },
  // 变量配置,有时路径只有在运行时才能知道,可以通过{locale}获取配置的值
  vars: {
    'locale': 'zh-cn'
  },
  // 映射配置
  map: [
    ['http://example.com/js/app/', 'http://localhost/js/app/']
  ],
  // 预加载项
  preload: [
    Function.prototype.bind ? '' : 'es5-safe',
    this.JSON ? '' : 'json'
  ],

  // 调试模式
  debug: true,

  // Sea.js 的基础路径
  base: 'http://example.com/path/to/base/',

  // 文件编码
  charset: 'utf-8'
});

此外,seajs.config 可以多次运行,每次运行时,会对配置项进行合并操作。

启动

seajs.use(['jquery', './main'], function($, main) {
  $(document).ready(function() {
    main.init();
  });
});

其它

获得文件(模块)的绝对路径
require.resolve('./foo.js'); // =>http://www.hukaihe.cn/static/foo.js
// 或
module.uri('./foo.js')
获得当前模块所依赖的模块
module.dependencies 

Seajs可以方便的跑在Nodejs端

// 让 Node 环境可以加载执行 CMD 模块
require('seajs');
var a = require('./a');

设计原则

  1. 关注度分离。比如书写模块 a.js 时,如果需要引用 b.js,则只需要知道 b.js 相对 a.js 的相对路径即可,无需关注其他。
  2. 尽量与浏览器的解析规则一致。比如根路径(/xx/zz)、绝对路径、以及传给 use 方法的非顶级标识,都是相对所在页面的 URL 进行解析。

AMD 与requirejs

RequireJS的目标是鼓励代码的模块化,它使用了不同于传统

baseUrl

RequireJS以一个相对于baseUrl的地址来加载所有的代码。 我们可以在require.config中对baseUrl进行设置,但如果未设置之,则默认与data-main所指定的文件为同一目录,如果未指定data-main属性,那么以引入requirejs的html地址为baseUrl。

此外RequireJS默认假定所有的依赖资源都是js脚本,因此无需在module ID上再加”.js”后缀。RequireJS脚本的加载是支持跨域的。

RequireJS使用head.appendChild()将每一个依赖加载为一个script标签。RequireJS等待所有的依赖加载完毕,计算出模块定义函数正确调用顺序,然后依次调用它们。

data-main

<script data-main="scripts/main" src="scripts/require.js"></script>
<script src="scripts/other.js"></script>

data-main指定的文件是异步加载的,所以不能保证main.js文件在other.js文件加载前完成加载。

模块定义与加载

下面是一个最基本的demo:

// zoo.js
define(['./lion','./tiger'], function(lion, tiger){
    var perform = function () {
        console.log('马戏团开演了');
        lion.perform();
        tiger.perform();
    }
    return {
        perform: perform
    }
})
// main.js
require(['./zoo'], function(zoo){
    zoo.perform();
})

requirejs也支持简单的键值对形式

define({
        color: "black",
        size: "unisize"
});

RequireJS的模块语法允许它尽快地加载多个模块,虽然加载的顺序不定,但依赖的顺序最终是正确的。同时因为无需创建全局变量,甚至可以做到在同一个页面上同时加载同一模块的不同版本。

严重不鼓励模块定义全局变量。遵循此处的定义模式,可以使得同一模块的不同版本并存于同一个页面上(参见 高级用法 )。另外,函参的顺序应与依赖顺序保存一致。

一个文件对应一个模块,但你可以使用优化工具,为每个模块生成模块名以将多个模块打成一个包,加快到浏览器的载人速度。

循环依赖

如果你定义了一个循环依赖(a依赖b,b同时依赖a),则在这种情形下当b的模块函数被调用的时候,它会得到一个undefined的a。b可以在模块已经定义好后用require()方法再获取(记得将require作为依赖注入进来):

//Inside b.js:
define(["require", "a"], function(require,  a) {
        //"a" in this case will be null if a also asked for b,
        //a circular dependency.
        return function(title) {
            return require("a").doSomething();
        }
    }
);

一般说来你无需使用require()去获取一个模块,而是应当使用注入到模块函数参数中的依赖。循环依赖比较罕见,它也是一个重构代码重新设计的警示灯。你也可以换成commonjs风格的代码

define(function(require, exports, module) {
    var a = require("a");
    exports.foo = function () {
        return a.bar();
    };
});

JSONP

require(["http://example.com/api/data.json?callback=define"],
    function (data) {
        //The data object will be the API response for the
        //JSONP data call.
        console.log(data);
    }
);

配置选项

配置方式
<script src="scripts/require.js"></script>
<script>
  require.config({
    baseUrl: "/another/path",
    paths: {
        "some": "some/v1.0"
    },
    waitSeconds: 15
  });
  require( ["some/module", "my/module", "a.js", "b.js"],
    function(someModule,    myModule) {
    }
  );
</script>

下面主要对几个require.config支持的配置项进行阐述

paths

设置path时起始位置是相对于baseUrl的,除非该path设置以”/”开头或含有URL协议(如http:)。此外,paths还可以进行备错处理:

    jquery: [
        'http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min',
        //If the CDN location fails, load from this location
        'lib/jquery'
    ]
shim

为那些没有使用define()来声明依赖关系、设置模块的”浏览器全局变量注入”型脚本做依赖和导出配置。注意:设置shim本身不会触发代码的加载。例如:

shim: {
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone',
            init: function (bar) {
               // 解决一些库的冲突
           }
        },
}
map

map用来对项目进行一些列加载的版本控制,如下代码,当“some/newmodule”调用了“require(‘foo’)”,它将获取到foo1.2.js文件;而当“some/oldmodule”调用“`require(‘foo’)”时它将获取到foo1.0.js,而其他模块调用foo时,则会获得foo.0.9。请在map配置中仅使用绝对模块ID,“../some/thing”之类的相对ID不能工作。

map: {
    '*':{
       'foo':'foo 0.9'
    }
    'some/newmodule': {
        'foo': 'foo1.2'
    },
    'some/oldmodule': {
        'foo': 'foo1.0'
    }
}
config

设置想要传递给具体模块的信息

requirejs.config({
    config: {
        'bar': {
            size: 'large'
        },
        'baz': {
            color: 'blue'
        }
    }
});

define(['module'], function (module) {
//Will be the value 'blue'
var color = module.config().color;
});
waitSeconds

在放弃加载一个脚本之前等待的秒数。设为0禁用等待超时。默认为7秒。

插件

domReady是最常见的插件,其作用是保证模块脚本执行之前页面已经完成加载。模块实现了Loader Plugin API,因此你可以使用loader plugin语法(注意domReady依赖的!前缀)来强制require()回调函数在执行之前等待DOM Ready。当用作loader plugin时,domReady会返回当前的document

require(['domReady!'], function (doc) {
});

注意: 如果document需要一段时间来加载(也许是因为页面较大,或加载了较大的js脚本阻塞了DOM计算),使用domReady作为loader plugin可能会导致RequireJS“超时”错。如果这是个问题,则考虑增加waitSeconds配置项的值,或在require()使用domReady()调用(将其当做是一个模块)。

AMD VS CMD比较

AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。

二者推崇的代码风格不同,CMD 推崇依赖就近,AMD 推崇依赖前置

对于依赖的模块,AMD 是提前执行,CMD 是延迟执行

二者最大的区别在于factory回调的执行时机不同

/* a.js */
define(factory);
/* b.js */
define(factory);
/* c.js */
define(function(require) {
 // BEGIN
  if(some_condition) {
    require('./a').doSomething();
  } else {
    require('./b').soSomething();
  }
  // END
});

在AMD模式下,c模块的 factory 在执行时,会接收 a 和 b 两个参数。这意味着,c 依赖的所有模块,都是在一开始就得执行好,即便有可能不需要执行。,换句话说,在 BEGIN 处,a 和 b 的 factory 都已经执行好。在 CMD 规范里,在 BEGIN 处,a 和 b 的 factory 还没未执行,在 END 处时,根据条件,只会执行其中一个。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值