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

來源:

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

你可能感兴趣的:(AMD、CMD、RequireJS)