AMD、CMD、RequireJS

为什么要使用模块化?

最主要的目的:

解决命名冲突

依赖管理

其他价值

提高代码可读性

代码解耦,提高复用性

CMD、AMD、CommonJS 规范分别指什么?有哪些应用

这三个规范都是为javascript模块化加载而生的,都是在用到或者预计要用到某些模块时候加载该模块,使得大量的系统巨大的庞杂的代码得以很好的组织和管理。模块化使得我们在使用和管理代码的时候不那么混乱,而且也方便了多人的合作。

CMD规范

CMD(Common Module Definition)通用模板定义,它是在一个浏览器端模块化的开发规范

使用CMD规范进行开发需要使用SeaJS

Sea.js推荐一个模块为一个文件

代码的书写格式:

define(id?,dependencies?,factory);

id:(可不写,默认文件名),用来定义模块的标识,通用文件名作为模块ID

dependencies:(可不写)一个当前模块依赖的模块名称数组(因为CMD推崇依赖就近,因此一般不在此处指定)

factory:

function(require,exports,module)

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

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

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

define Function

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

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

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

define({ “foo”:“bar” });

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

define(function(require,exports,module){})

define define(id?, deps?, factory)

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

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

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

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

define.cmd Object

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

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

require Function

require是factory函数的第一个参数

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

define(function(require, exports){// 获取模块 a 的接口vara =require('./a');// 调用模块 a 的方法a.doSomething();});

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=newSomeClass();// exports 不再等于 module.exportsconsole.log(module.exports===exports);// false});

注意:对 module.exports 的赋值需要同步执行,不能放在回调函数里。下面这样是不行的:

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

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

// y.jsdefine(function(require, exports, module){varx =require('./x');// 无法立刻得到模块 x 的属性 aconsole.log(x.a);// undefined});

小结

经常使用的 API 只有 define, require, require.async, exports, module.exports 这五个。其他 API 有个印象就好。

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

AMD规范

AMD (Asynchronous Module Definition, 异步模块定义) 指定一种机制,在该机制下模块和依赖可以异步加载。这对浏览器端的异步加载尤其适用。

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

使用AMD规范进行开发需要使用RequireJS

requireJS主要解决两个问题:

js文件之间的依赖关系:被依赖的文件需要早于依赖它的文件加载到浏览器

js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长

define(id?, dependencies?, factory);

id: 定义中模块的名字,可选;如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。

依赖dependencies:是一个当前模块依赖的,已被模块定义的模块标识的数组字面量。 依赖参数是可选的,如果忽略此参数,它应该默认为["require", "exports", "module"]。然而,如果工厂方法的长度属性小于3,加载器会选择以函数的长度属性指定的参数个数调用工厂方法。

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

AMD模式

define和require这两个定义模块、调用模块的方法,合称为AMD模式。它的模块定义的方法非常清晰,不会污染全局环境,能够清楚地显示依赖关系。

AMD模式可以用于浏览器环境,并且允许非同步加载模块,也可以根据需要动态加载模块。

define方法:定义模块

define方法用于定义模块,RequireJS要求每个模块放在一个单独的文件里。

按照是否依赖其他模块,可以分成两种情况讨论。第一种情况是定义独立模块,即所定义的模块不依赖其他模块;第二种情况是定义非独立模块,即所定义的模块依赖于其他模块。

require方法:调用模块

require方法用于调用模块。它的参数与define方法类似。

require(['foo','bar'],function( foo, bar ){        foo.doSomething();});

上面方法表示加载foo和bar两个模块,当这两个模块都加载成功后,执行一个回调函数。该回调函数就用来完成具体的任务。

require方法的第一个参数,是一个表示依赖关系的数组。这个数组可以写得很灵活,请看下面的例子。

require( [window.JSON ?undefined:'util/json2'],function( JSON ){JSON=JSON||window.JSON;console.log(JSON.parse('{ "JSON" : "HERE" }') );});

上面代码加载JSON模块时,首先判断浏览器是否原生支持JSON对象。如果是的,则将undefined传入回调函数,否则加载util目录下的json2模块。

require方法也可以用在define方法内部。

define(function(require){varotherModule =require('otherModule');});

CommonJS规范

CommonJS是服务器端模块的规范,Node.js采用了这个规范。Node.JS首先采用了js模块化的概念。

1.在一个模块中,存在一个自由的变量“require”,它是一个函数。

这个“require”函数接收一个模块标识符

“require”返回外部模块所输出的API

如果出现依赖闭环(dependency cycle),那么外部模块在被它的传递依赖(transitive dependencies)所require的时候可能并没有执行完成;在这种情况下,”require”返回的对象必须至少包含此外部模块在调用require函数(会进入当前模块执行环境)之前就已经准备完毕的输出。

如果请求的模块不能返回,那么”require”必须抛出一个错误。

2.在一个模块中,会存在一个名为“exports”的自由变量,它是一个对象,模块可以在执行的时候把自身的API加入到其中。

3.模块必须使用“exports”对象来做为输出的唯一表示。

根据这个规范,每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。

// example.jsvarx =5;varaddX =function(value){returnvalue + x;};

上面代码中,变量X和函数addX,是当前文件example.js私有的,其他文件不可见。如果想在多个文件分享变量,必须定义为global对象的属性。

global.warning =true;

上面代码的warning变量,可以被所有文件读取。当然,这样写法是不推荐的。

CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。

varx =5;varaddX =function(value){returnvalue + x;};module.exports.x = x;module.exports.addX = addX;

上面代码通过module.exports输出变量x和函数addX。

require方法用于加载模块。

varexample =require('./example.js');console.log(example.x);// 5console.log(example.addX(1));// 6

CommonJS模块的特点:

所有代码都运行在模块作用域,不会污染全局作用域。

模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。

模块加载的顺序,按照其在代码中出现的顺序。

作者:Eazer

链接:https://www.jianshu.com/p/c9177fbbe9aa

來源:简书

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容

  • 为什么要使用模块化? 最主要的目的:解决命名冲突依赖管理 其他价值提高代码可读性代码解耦,提高复用性 CMD、AM...
    Eazer阅读 656评论 3 1
  • 题目1: 为什么要使用模块化? 网页愈趋向Web应用程序,导致JavaScript的体量愈来愈大,客户端的代码模块...
    cctosuper阅读 285评论 0 0
  • 题目1: 为什么要使用模块化? 最主要的目的: 解决命名冲突 依赖管理 其他价值 提高代码可读性 代码解耦,提高复...
    曾祥辉阅读 141评论 0 0
  • 文/长安有间铺子 海底月是天上月,眼前人是心上人。 我想每个女孩子都曾期待过能遇到一个人,幻想着与他的幸福生活会是...
    长安有间铺子阅读 582评论 1 3
  • 1、void remove(List a,Integer b) 删除链表a中所有大于b的表项。 2、创建线程的3...
    黄云丰阅读 277评论 0 0