在node.js中我们对require这个词应该很熟悉,引用一个模块,但什么是一个模块呢? 它的定义方式是什么样的呢? 在服务器端和浏览器端我们对模块的请求方式是相同的吗? 这也就引出了我们题目中出现的几个词 commonjs amd cmd
1)commonjs
为什么commonjs javascript本身不存在现成的模块化系统,为了提高js程序的可移植性和可交互性,有必要定义一套规范来实现js的模块化定义,这样就出现的commonjs
如何定义一个符合commonjs规范的模块
commonjs模块的定义分为 模块引用 模块定义 模块标识3个部分
模块引用 引用一个模块到当前的上下文中 例如我们在node中 var http = require('http') 这里就引用了http模块到当前的上下文中,我们可以通过require()的返回值http来使用http模块中定义的属性或者方法
模块定义 我们通过require()可以使用另一个模块的属性或者方法,与此对应的当前上下文提供的exports对象用于到处当前模块定义的属性或者方法(它是模块唯一的对外的接口) 在模块中,还存在一个module对象,它代表这个模块自身而exports对象正是module的属性 ,将方法定义成exports对象的属性就可以将方法导出供引用的模块使用
模块标识 模块标识就是require()中的参数,必须符合小驼峰的命名规则 可以以相对路径或者绝对路径 也可以省略.js的后缀名
下面来定义一个简单的符合commonjs规范的模块
/* require其他模块的话写在这里 */ function add(a,b) { console.log(a+b); } //在这里写方法 可以是一些内部的方法或者导出的方法 module.exports = function() { return { adduse:add } }(); //通过返回一个对象 在对象调用方法来使用模块中定义的方法(调用adduse)
使用上面的模块
var test = require("./test.js"); //require的是相对路径的文件 test.adduse(1,5);
在node中运行文件 发现输出了结果6
上面的例子中有几个点 require它是同步的 也就是当我们通过require加载需要的模块的时候,必须等它返回结果后才能执行后面的语句(我们对同一个模块require多次的时候,不会重复加载),我们可以想象在需要require很多模块的时候,是存在一定的时间延迟的(服务器端从本地磁盘读取数据,这个时间还是可以接受的) 浏览器端从服务器读取模块,并且这些模块是已同步模式加载的话,甚至会造成页面暂时的失去响应,这并不是我们想要的结果
module.exports exports 都可以导出方法,他们的区别呢
module.exports是模块导出方法的唯一的接口,exports是module.exports的一个辅助 通过exports收集到的方法最终都复制给了module.exports,前提是module.exports本身不具备任何方法,如果module.exports已经存在相应的方法的话,exports收集的方法会忽略(exports只是module.exports的一个地址的引用)
看下面的例子
var num = 0; exports.count = function() { num++; console.log(num); }; module.exports = function(){ num = num + 10; this.count = function() { console.log(num); } };//在这里就失去了exports赋值给module.exports的值
var isEq = (exports === module.exports) console.log(isEq);
调用上面的模块
var test = require('./test.js'); var a = new test(); a.count(); //输出false 10
注意上面的调用 module.exports输出的是一个函数对象,我们必须实例化它来调用它的方法
amd
为什么amd 同步的方式在浏览器端加载模块的方式存在着导致页面延迟的可能 ,并且模块中的依赖项不好管理,也就是说在我们的页面中必须按js文件依赖的先后顺序去加载文件,如果顺序出现错误就会出现错误所以提出了异步方式的加载模块
异步加载模块方式 在加载模块的时候,不影响后面语句的执行,在回调函数中执行依赖这个模块的语句(在所有的依赖模块加载完成后调用回调函数)
定义一个符合amd的模块 define(['dep1','dep2'],function(dep1,dep2){...});
dep1,dep2 是当前模块依赖的数组,将模块加载后的引用以参数的方式传入回调函数,在回调中操作依赖的模块,去实现定义的模块
require.js 就帮助我们实现了异步加载模块的方式而且它加载的模块都要符合amd的规范,也就是上面的amd模块的定义 require.js 更好的帮助我们管理了模块之前的依赖
require.js 使用
(1)首先在页面中引入require.js <script src="require.js" data-main="main"></script> 上面我们引入了require.js 我将require.js 和所有的js放在同一级目录下
data-main 指向的是程序运行的主程序main.js
(2)编写main.js 在main.js中 如果我们引用了其他的模块 就要通过这种方式定义 require(['moduleA','moduleB','moduleC'],function(moduleA,moduleB,moduleC){操作在这里进行})
这里的第一个参数是一个数组,当这些模块都加载完成后,会通过参数的方式传递到回调函数中,全部加载完成后会调用回调函数,进行操作
下面来实现一个简单的例子
先定义一个简单的模块
define([],function(){ var add = function(a,b) { console.log(a+b); }; return { add:add } }); //这个模块并没有引用其他的模块 它对外提供了一个add方法
接下来我们在main.js中引用这个模块
require(['math','jquery'],function(math,$){ $(document).ready(function(){ math.add(1,2); }); });
main中引用了jquery和刚才定义的math并且是在jQuery实现的方法中调用的模块math提供的方法 ,在网页中输出了3
如何加载不符合amd规范的模块 就要通过config进行设置预先定义它的一些特征 比如加载backbone.js时候,它是需要underscore jquery的,也就是当我们需要依赖backbone的时候,需要它自动去加载underscore 和 jquery
下面我们通过一个例子来演示一下如何定义需要加载其他模块的模块
首先我们定义了一个模块math 它默认是依赖jquery的 但是我们在定义它的时候并没有在参数中添加它 (按之前的思路 我们在浏览器中加载这些文件的时候 jquery就必须在math文件之前被添加 )
define([],function(){ var add = function(a,b){ $(document).ready(function(){ console.log(a+b); }); } return { add : add } });//jquery提供的别名是$ 也就是当我们已经引用了jquery的情况下,调用$就是使用jquery使用jquery给我们提供的方法
下面我们来使用math.js
require.config({ shim:{ 'math': { deps:['jquery'] } } }); //这个就是刚加载之前的设置 当我们要加载math的时候,它会默认的为我们加载jquery 有个模块并没有提供exports 我们也可以在这里设置 require(['math'],function(math){ math.add(1,2); }); //在浏览器的控制台输出了3
什么是cmd
cmd是通用模块定义 在cmd中一个模块就是一个文件 我们在回顾下amd模块的定义方式 define('exports',['moduleA','moduleB'],function(moduleA,moduleB){...}) amd中第一个参数是表示该模块的标识符 第二个参数是这个模块所依赖的数组,第三个参数是一个需要实例化的工厂或者对象
而cmd 中定义模块的方式是 define(factory) 这个factory可以是一个函数,对象或者字符串 当factory为函数的时候,表示该模块的构造方法,执行该构造方法便可以得到该模块对外提供的接口
cmd和amd的区别是我们依赖的位置 在amd中我们必须要求提前加载定义该模块的依赖项 而cmd中推荐我们就近的加载依赖项,也就是在factory中去加载依赖项
下面仔细的介绍下cmd定义的factory方法
第一个参数是require 用于在factory中加载其他的依赖模块
第二个参数是exports 用于向外提供对外的接口(你也可以通过return返回对外提供的接口)
第三个参数module是一个对象上面存储了一些与当前模块相关的属性与方法
跟require.js对amd的支持一样 对cmd的支持就要使用sea.js
下面我们来一个简单的例子 定义一个模块 使用它(这里的math.js main.js sea.js 处于同一级的目录 页面位于上一级目录中)
define(function(require,exports,module){ module.exports = { add : function(a,b) { console.log(a+b); } }; }); //这里我们定义了一个模块 它对外导出一个函数
在程序入口的时候,引用这个模块 ,并且调用了模块对外提供的
define(function(require,exports,module){ var math = require('./math'); math.add(1,2); });
在require.js中通过data-main 制定程序的入口 在sea.js中我们可以在页面中调用seajs.use() 来定义程序的主入口
<script type="text/javascript" src="js/sea.js"></script>
<script type="text/javascript">
seajs.use('./js/main');
</script> //在页面的控制台输出了3 (这里当程序需要加载多个文件的时候就可以通过seajs.use(['./a','./b'],function(a,b){...}) 如果只有一个入口文件的时候,也可以像require.js 那样通过data-main指定入口文件