为什么要使用模块化?
最主要的目的:
解决命名冲突
依赖管理
其他价值
提高代码可读性
代码解耦,提高复用性
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
來源:
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。