Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

CMD 模块定义规范 #242

Closed
lifesinger opened this Issue · 55 comments
@lifesinger
Owner

CMD 模块定义规范

在 Sea.js 中,所有 JavaScript 模块都遵循 CMD(Common Module Definition) 模块定义规范。该规范明确了模块的基本书写格式和基本交互规则。

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

define(factory);

define Function

define 是一个全局函数,用来定义模块。

define define(factory)

define 接受 factory 参数,factory 可以是一个函数,也可以是一个对象或字符串。

factory 为对象、字符串时,表示模块的接口就是该对象、字符串。比如可以如下定义一个 JSON 数据模块:

define({ "foo": "bar" });

也可以通过字符串定义模板模块:

define('I am a template. My name is {{name}}.');

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

define(function(require, exports, module) {

  // 模块代码

});

define define(id?, deps?, factory)

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

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

  // 模块代码

});

iddeps 参数可以省略。省略时,可以通过构建工具自动生成。

注意:带 iddeps 参数的 define 用法不属于 CMD 规范,而属于 Modules/Transport 规范。

define.cmd Object

一个空对象,可用来判定当前页面是否有 CMD 模块加载器:

if (typeof define === "function" && define.cmd) {
  // 有 Sea.js 等 CMD 模块加载器存在
}

require Function

requirefactory 函数的第一个参数。

require require(id)

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

define(function(require, exports) {

  // 获取模块 a 的接口
  var a = require('./a');

  // 调用模块 a 的方法
  a.doSomething();

});

注意:在开发时,require 的书写需要遵循一些 简单约定

require.async require.async(id, callback?)

require.async 方法用来在模块内部异步加载模块,并在加载完成后执行指定回调。callback 参数可选。

define(function(require, exports, module) {

  // 异步加载一个模块,在加载完成时,执行回调
  require.async('./b', function(b) {
    b.doSomething();
  });

  // 异步加载多个模块,在加载完成时,执行回调
  require.async(['./c', './d'], function(c, d) {
    c.doSomething();
    d.doSomething();
  });

});

注意require 是同步往下执行,require.async 则是异步回调执行。require.async 一般用来加载可延迟异步加载的模块。

require.resolve require.resolve(id)

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

define(function(require, exports) {

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

});

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

exports Object

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

define(function(require, exports) {

  // 对外提供 foo 属性
  exports.foo = 'bar';

  // 对外提供 doSomething 方法
  exports.doSomething = function() {};

});

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

define(function(require) {

  // 通过 return 直接提供接口
  return {
    foo: 'bar',
    doSomething: function() {}
  };

});

如果 return 语句是模块中的唯一代码,还可简化为:

define({
  foo: 'bar',
  doSomething: function() {}
});

上面这种格式特别适合定义 JSONP 模块。

特别注意:下面这种写法是错误的!

define(function(require, exports) {

  // 错误用法!!!
  exports = {
    foo: 'bar',
    doSomething: function() {}
  };

});

正确的写法是用 return 或者给 module.exports 赋值:

define(function(require, exports, module) {

  // 正确写法
  module.exports = {
    foo: 'bar',
    doSomething: function() {}
  };

});

提示exports 仅仅是 module.exports 的一个引用。在 factory 内部给 exports 重新赋值时,并不会改变 module.exports 的值。因此给 exports 赋值是无效的,不能用来更改模块接口。

module Object

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

module.id String

模块的唯一标识。

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

  // 模块代码

});

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

module.uri String

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

define(function(require, exports, module) {

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

});

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

module.dependencies Array

dependencies 是一个数组,表示当前模块的依赖。

module.exports Object

当前模块对外提供的接口。

传给 factory 构造方法的 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 的赋值需要同步执行,不能放在回调函数里。下面这样是不行的:

// x.js
define(function(require, exports, module) {

  // 错误用法
  setTimeout(function() {
    module.exports = { a: "hello" };
  }, 0);

});

在 y.js 里有调用到上面的 x.js:

// y.js
define(function(require, exports, module) {

  var x = require('./x');

  // 无法立刻得到模块 x 的属性 a
  console.log(x.a); // undefined

});

小结

这就是 CMD 模块定义规范的所有内容。经常使用的 API 只有 define, require, require.async, exports, module.exports 这五个。其他 API 有个印象就好,在需要时再来查文档,不用刻意去记。

与 RequireJS 的 AMD 规范相比,CMD 规范尽量保持简单,并与 CommonJS 和 Node.js 的 Modules 规范保持了很大的兼容性。通过 CMD 规范书写的模块,可以很容易在 Node.js 中运行,后续会介绍。

祝使用愉快,有任何想法建议,欢迎反馈留言。

@lifesinger
Owner

有任何问题,欢迎留言交流。
注意:已解决的问题,会在整理后删除掉。

@lifesinger lifesinger closed this
@hanyangecho

请问module.id主要做什么用?用于那些场景?

@lifesinger
Owner

@hanyangecho 有些情况下,我们书写模块时,会手写 id:

define('xxx', fn)

module.id 用来存储原始的 xxx 值。一般情况下没有用,在写插件时,有时会用到。id 是模块信息的一部分,保持完备性。

@fjarcticfox

想知道seajs维护的持续性怎么样?是否会有团队长期维护?

@afc163
Owner

@fjarcticfox 这个你翻翻最近的 commit 和 issues 大概就能知道这个社区的活跃程度了。

@xuexb

seajs里的define.cmd不是undefined吗? 如果用它判断就挂了. seajs 2.0里

@afc163
Owner

@xuexb 2.1 加回来了。

@xuexb

模块依赖问题
//xl.js
define("xl",function () {
//code
return {
name:"is name",
age:"24"
}
});

//home.js
define("home",["xl"],function(){
//这里依赖模块xl,但怎么用模块xl呢?因为模块xl没有全局对象,普通的时候地用var xl=require("xl");而使用依赖应该怎么用呢
});

@afc163
Owner

@xuexb

var xl=require("xl") 对,就这么用。

@xuexb

那加不加模块依赖都得用require来引用, 那依赖表示?

@lifesinger
Owner

var xl = require('xl') 就这么用啊

@xuexb

问个疑惑哦:
每个看着没事, 但连起来就感觉怪怪的.

以下为引用

  • Sea.js 中,所有 JavaScript 模块都遵循 CMD(Common Module Definition) 模块定义规范。
  • define 也可以接受两个以上参数。字符串 id 表示模块标识,数组 deps 是模块依赖。
  • 注意:带 id 和 deps 参数的 define 用法不属于 CMD 规范,而属于 Modules/Transport 规范。
  • iddeps 参数可以省略。省略时,可以通过构建工具自动生成

上述表明, id,deps可省略,而工具会自动生成. 又提到带他们的不是CMD规范...会有种误会的感觉

@beijingme

麻烦帮忙看下我写的哪里有问题,拜托!

问题1:jquery无法加载

seajs.config({
        paths: {
            'static': 'http://192.168.2.141:96/js/seajs'    
        },
        alias:{
            'jq': 'static/global/jquery-1.9.1.js'   
        }   
    });
seajs.use('static/test/main');

//main.js
define(function (require, exports, module) {
    var $ = require('jq'); //为什么是null
    var Preson = require('./preson');
    var p1 = new Preson('jack', 31);
    p1.say();
});

问题2:这种写法 Preson对象是null

define('static/test/main', ['jq'], function (require, exports, module) {
    var $ = require('jq');
    var Preson = require('./preson');
    var p1 = new Preson('jack', 31);
    p1.say();
});
@lifesinger
Owner

@beijingme

问题1应该是 jquery-1.9.1.js 中 define 的 id 不对,参考 #930

问题2是你写错了,依赖应该是 ['jq', './preson']

@hkongm

用第一种写法吧,看看networks里有没有jq.js的请求。
另外,应该是person,不是preson XD

@beijingme

@lifesinger
谢谢,第一问题我通过使用处理好的jquery已经好了,第二个问题,是我太马虎,也感谢hkongm

@hite

语法错误,有点强迫症~。。举例的代码里doSomething属性的值后面 多了分号。

// 正确写法
  module.exports = {
    foo: 'bar',
    doSomething: function() {};
  };
@lifesinger
Owner

@hite 感谢指正,已修改。

@qidizi

require.async异步都不能保证模块加载成功才回调?是否逻辑应该改变成模块加载成功时才回调呢?假设情景是我需要jquery才能使用,但是它却不管加载成功与否(其实只是保证加载运行结束触发了onload)就进行回调,那么,这样,我就得自己保证 if (!$) throw new Error('没有jquery?'),

@afc163
Owner

@qidizi 空说无凭,给 demo

@iwinmin

@qidizi 一般都会先运完JS文件后才触发onload么? 应该是这样

@qidizi

@afc163
t.htm

<script src="https://a.alipayobjects.com/seajs/seajs/2.1.1/sea.js" id="seajsnode"><!--使用阿里的cdn--></script>
<script>
seajs.config({

  // 别名配置
  alias: {
    'jquery': 'AraleCDN/jquery/jquery/1.10.1/jquery',
    'jquery2': 'AraleCDN/jquery/jquery/1.10.1/jquery-not-exist.js#',
    'cookie':'AraleCDN/arale/cookie/1.0.2/cookie'
  },

  // 路径配置
  paths: {
    'AraleCDN': 'https://a.alipayobjects.com',
    'root':'./'
    },

  // 调试模式
  debug: true,
  // 文件编码
  charset: 'utf-8'
});
//主入口
seajs.use('root/d');
</script>

d.js

// 所有模块都通过 define 来定义
define(function(require, exports, module) {
    var $ = require('jquery');
    alert($);
    require.async('jquery2',function($){alert($);});
});

第一个弹出正确,
第二个弹出null,因为jqueyr不存在,但是还是加载成功了,至于像ie6之类没有onerror事件,可能实现这个有点麻烦,很难知道js是加载成功了还是加载失败了,反正还是会触发加载完成的事件

@afc163
Owner

@qidizi 可以看看 #453 #921

@qidizi

所以说,最终模块加载成功与否还是得自己来做保证.它只负责加载步骤,成功与否是自己来保证,我意思就是说是否机制能保证,看来不行,就得逻辑代码上来保证这个依赖性了.

@lifesinger
Owner
@ghost

我能否简单理解,所谓CMD和AMD就是提供一定的基础函数(从面向对象来说:就是提供一定的接口)

@afc163
Owner

提供了一种语法。

@BO-HAI
require.async(id, callback?)

是否会返回一个Promise对象,让我的回调平行化,我看没有返回,我不想嵌套回调该怎么做?

@army8735
Owner

Promise要加就太大了

@lifesinger
Owner
@jyk0011

看了还几天了,还是有点晕

@jyk0011

这个论坛的形式也有点晕。还不会用呢。

@zhishaofei3
define(['jquery'], function(require, exports, module) {
  var $ = require("jquery");
  // 模块代码
});

我想问一下 这里写['jquery']的作用是什么? 跟不写['jquery']有什么区别?
CMD是不是只支持但不提倡这种写法?

@jyk0011
@zhishaofei3

@jyk0011 是依赖的意思,但是我感觉不写也行 编译的时候自动会写上啊

@Ihuali

50分钟都没有入门,。。。

@lichunqiang

@Ihuali 我用了几个月才走到门口

@hahnzhu hahnzhu referenced this issue in hahnzhu/hahnzhu.github.com
Open

模块加载器 #21

@xvzone

通过exports对象增加成员向外提供接口 和
使用 return 直接向外提供接口 这两种方式有什么区别意义,请指教

@afc163
Owner

exports 是 CommonJS 规范。而 return 是 AMD 和 CMD 规范的浏览器式的输出方式,比如在 node 上就不好用了。

@ikitty

@xuexb @jyk0011

在 2014-06-25 10:40:19,"zhishaofei" notifications@github.com 写道:
define('hello',['jquery'],function(require,exports,module){var$=require("jquery");// 模块代码});
我想问一下 这里写['jquery']的作用是什么? 跟不写['jquery']有什么区别?

我也有这样的疑惑,既然在模块内引用依赖文件需要使用require语法,那么这里的deps参数有什么作用的。
我自己测试,即使去掉了第二个参数(依赖数组),对后面使用require引入依赖也不会有任何影响。

@afc163
Owner
@younth

比如当模块的接口是某个类的实例时,需要通过 module.exports 来实现:暴露一个类的实例不是这样:
exports.Slide = new Slide();对外接口是slide,怎么用module.exports暴露呢。。求解释

@nuysoft nuysoft referenced this issue in thx/brix-loader
Closed

代码风格 bx-id or data-module #1

@greatming

请教问题
index.html

seajs.config({
    base: "/assets/js/",
    alias: {
        "underscore": "underscore-min.js",
        "jquery": "jquery.js"
    }
});

seajs.use('sea-modules/article/index');

index.js

define(function(require, exports, module) {

var aa = require('jquery');

alert(aa);  //null
 alert(jQuery);   // 可以打印出 对象

});

为什么上面 打印aa 为 null啊

@liujiangfeng
@greatming
@lichunqiang

真的,这个是个烂大街的问题哦。可以在issue里面搜索下,一大丢

@liujiangfeng
@greatming

seajs.config({
base: "/assets/js/",
alias: {
"underscore": "underscore-min.js",
"jquery": "jquery.js"
}
});

seajs.use('sea-modules/article/index');

@liujiangfeng
@greatming

没有,只有引用了 < script src="/assets/js/sea/sea-debug.js">< /script>

并且 在 页面中

seajs.use("jquery",function($){
alert($); //null
alert(jQuery); //function
})

效果也是一样的

@greatming

是不是要对 jquery.js 源码文件进行 cmd模块改造啊

@imyelo imyelo referenced this issue from a commit in imyelo/pack
@imyelo imyelo add seajs(cmd) support 3d270b0
@yreenchan

require和require.async不可以同时存在吗?我两个同时使用时,如果require在前,require.async无法加载文件,如果require.async在前,则可以加载文件,但无法执行回调,这是什么情况,求解释!

@swit1983

define 中的id参数是不是只有在页面上写的时候有用?第二个依赖参数我加载后,没执行里面的alert方法,看到js文件已经加载了奇怪的很

@yishou

不能exports={};的问题,只要sea.js里面加个判断就可以
if(exports!=module.exports){
module.exports=exports;
}

何必让使用者这么纠结

@afc163
Owner

@yishou 这是 CommonJS Modules/1.0 的规范,不是 Sea.js 定的。可以看看 Node.js 的说明。https://nodejs.org/docs/latest/api/modules.html#modules_exports_alias

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.