今天主要说说AMD规范在Web环境里的应用,以及为什么采用AMD规范.想了解AMD规范的可以点击这里
what AMD?
- 规定
define
来定义一个模块 - 规定
require
来请求一个模块 - 规定
exports
来导出一个模块对外公共的API - 约定通用的模块依赖规范
使用AMD之前是怎么实现一个模块的呢?
下面是一个简单模块的例子.
(function(global){
var module = {
prop: 'name',
method: function(){ // do stuff}
}
global.moduleName = module;
})(this);
基本上没有使用AMD之前一般都是这样实现一个模块的:
- 定义一个立即执行的函数来导出一个模块公共API
- 使用script标签来加载模块
- 使用一定的script加载顺序来控制模块之前的依赖
当项目很大的时候,这种方式的模块开发几乎不能维护,而且部署的时候也是一个大问题,通常需要对业务进行脚本化来简化部署.
另一个模块规范COMMONJS
这里COMMONJS模块先简写为CJS.
起初CJS规范是在NodeJS里使用的,看下面的一段简单的代码.
var _ = require('underscore');
exports.module = {
find: function(){ // do stuff use _ }
}
在CJS眼里,模块是阻塞的方式来加载的,这样才能保证后面的代码可以取得模块实例,但是这个跟浏览器是天生的不兼容,因为浏览器端的脚本都是异步加载的,像上面的代码在浏览器端就会报错,假如在exports里的使用_
的话。
有人说,可以在浏览器端模拟require
来异步加载脚本,确实,使用xhr
然后解析脚本内容来获取模块实例,但是这种方式有很多缺点,详情可以看我之前写的文章,为什么需要web模块
说说AMD
先上一个以AMD规范定义的模块代码
define(['underscore'], function(_){
var module = {
// do stuff use _
};
return module;
});
相比较于普通的立即函数,以define方式定义的模块没有在全局下增加额外的东西,而且是传递数组方式的参数来解决模块依赖的问题,最后一个回调函数是在前面所有的依赖加载完成之后执行.
AMD是上面那种以‘XHR和动态解析脚本内容’方式的改进,提高了可编程性以及调试,方便项目构建
AMD模块支持定义模块名称,默认第一个参数是模块名称,看下面的代码
define('demoMd', ['underscore'], function(_){
var module = {
// do stuff use _
}
return module;
});
其实最佳的模块定义规范是,模块名称是可以不写的,这样可以提高模块的重用性,而且一个文件最后只定义一个模块,这样相同的模块定义文件可以保证它的独立性,最终的模块名称会根据不同的AMD规范实现在内部定义的.
使用模块名称的地方,其实只有在项目最后构建的时候,合并多个模块文件,构建工具会自动生成模块名称
AMD规范本身也提供了对CJS规范的兼容,看下面以AMD规范定义的CJS兼容的代码.
define(function(require){
var _ = require('underscore');
var module = {
// do stuff use _
}
return module;
});
上面的代码其实只是AMD规范的一个语法糖,最终会转变成下面的形式
define(['require','underscore'], function(require, _){
var _ = require('underscore');
var module = {
// do stuff use _
}
return module;
});
回调函数的主体代码不会变,只是对它进行了包装,保证是以数组方式提供的依赖
但是这种语法有一个前提,require
方法里的参数只能是字符串字面值,而不能是变量,或者有判断逻辑的加载,因为这种转换的原理是通过调用Function.prototype.toString
,来转换的,所以不能解析变量或者逻辑,像下面的代码就是错误的
define(['require','underscore'], function(require, _){
var reqName = 'underscore';
var _ = require(reqName);
/*
or like this
if (some){
_ =require('abc');
}else{
_ = require('asdf');
}
*/
var module = {
// do stuff use _
}
return module;
});