AMD、CMD、CommonJs
是ES5
中提供的模块化编程的方案,ES6 module的import/export
是ES6
中定义新增的
ES6
之前,社区指定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器
,ES6
之后,实现了模块功能,而且实现相当简单,完全可以取代CommonJS和AMD
规范,成为浏览器和服务器通用的模块解决方案。
模块化:就是将不同的功能的函数按模块封装起来,并提供使用接口,他们彼此之间互不影响,一个模块就是一个文件
AMD
规范是RequireJS
在推广过程中对模块定义的规范化产出,它是一个概念,RequireJS
是对这个概念的实现.AMD
是一个组织,RequireJS
是在这个组织下自定义的一套脚本语言.
AMD规范
采用的是异步的模块加载机制,它的加载不影响后面文件的加载,所有依赖这个模块的语句都放在一个回调函数里,等到所有都加载完成,才执行这个回调函数.
AMD
是在require.js
推广过程的产物,推崇依赖前置,提前执行.即使没有用到的模块,也提前加载,require.js
采用require.config()
定义模块路径,define()
定义依赖模块,使用require
加载模块
以前我们开发一个网页,会把一个网页上所有需要的js放上面,看上去既臃肿又复杂,这种通过script
标签来导入一个个的js
文件这种方式已经不能满足现状互联网开发模式,我们需要团队协作,模块复用,单元测试,等一系列复杂的需求.
require.js 会在相关的js加载后执行回调函数,这个过程是异步的,所以可以防止js加载阻塞页面渲染
一个模块就是一个文件
require用来加载依赖模块,并执行加载完后的回调函数
(1) 第一个参数是依赖模块列表
(2) 第二个参数是一个callback
函数
用来配置模块加载位置,简单点说就是引入模块并起一个别名
比如将百度的jquery库地址标记为jquery,这样在require时只需要写[“jquery”]就可以加载该js本地的js模块我们也可以这样配置
我们不可能每个页面都重复配置require.config
,如果每个页面都加入这样的配置,那肯定不好看,所以require.js
提供了一种叫"主数据"的功能
(1)首先,我们在项目中创建一个main.js
,作为主入口文件,在main.js
里进行require.config
的配置
(2)然后我们在页面中使用下面的方式来使用require.js
/** 网页中引入require.js及index.main.js **/
<script src="js/require.js" async="true" defer data-main="js/index.main.js"></script>
加载require.js
的script标签加入了data-main
属性(设置入口文件
),这个属性指定的js
将在require.js
加载完成后处理,我们把require.config
的配置加入到data-main
后,就可以使每个页面都使用这种配置,然后页面中就可以直接用require
来加载所有的短路径模块名
用来定义一个模块
//第一种方式
define({
//key:value
})
//第二种方式
define(function(){
return {
}
});
//第三种方式(推荐使用此种方式)
define(['依赖的模块路径'],function(依赖模块名称或别名){
return{
}
});
//第四种方式(官方不推荐)
define('模块名称', ['依赖的模块路径'], function(依赖模块名称){// 官方不推荐
// ...
return {
// ...
};
});
推荐使用第三种方式
/** 网页中引入require.js及index.main.js **/
/** index.main.js 入口文件 **/
//使用config()方法配置路径地址
require.config({
baseUrl: "js/lib",
paths: {
"jquery": ["http://libs.baidu.com/jquery/2.0.3/jquery","jquery-1.10.1.min"],//全路径
"cookie": "jquery.cookie",
"details": "details",//实际路径为js/lib/details.js
"banner": "banner"
},
shim: {
//设置依赖关系
"cookie": ["jquery"]
}
});
//引入模块,调用执行 (require用来加载依赖模块,并执行加载完后的回调函数)
require(["details", "banner"], function (details, banner) {
details.init();
});
/** 主功能模块的实现 details.js **/
//u,$ 为当前引入的模块起的别名
define(['urlParm', "jquery", "cookie"], function (u, $) {
//实现功能
});
CMD规范
是SeaJS
在推广过程中对模块定义的规范化产出,是一个同步模块定义
但是它是异步加载的,是SeaJS
的一个标准,SeaJS
是CMD
概念的一个实现,SeaJS
是淘宝团队提供的一个模块开发的js
框架
CMD规范
同AMD规范
一样,也是并行加载所有依赖的模块,但是不会立即执行模块,等到真正需要(require)
的时候,才开始解析,也就是说,CMD是延迟执行的,也就是懒执行
SeaJS
是CMD
概念的一个实现,推崇依赖就近,延迟执行
/** CMD写法 **/
define(function(require, exports, module) {
var a = require('./a'); //在需要时申明
a.doSomething();
if (false) {
var b = require('./b');
b.doSomething();
}
});
与require.js
一样,同样也是为了解决文件之间依赖问题,防止js加载阻塞页面渲染
是一个全局函数,用来定义模块
define接收一个参数,参数可以是函数,也可以是对象,也可以是字符串,下面讲的是参数为函数时
define(function(require, exports, module) {
// 模块代码
});
define中参数为函数时,表示是模块的构造方法,执行该构造方法,可以得到模块向外提供的接口,该构造方法接收3个参数require
、exports
和 module
(1) require 是一个方法,用来接受模块标识(模块路径)
作为唯一参数,用来获取其他模块提供的接口
define(function(require, exports) {
// 获取模块 a 的接口
var a = require('./a');
// 调用模块 a 的方法
a.doSomething();
});
(2)exports 是一个对象,用来向外提供模块接口
define(function(require, exports) {
// 对外提供 foo 属性
exports.foo = 'bar';
// 对外提供 doSomething 方法
exports.doSomething = function() {};
});
(3)module 是一个对象,上面存储了与当前模块相关联的一些属性和方法.一般会使用module.exports
来给当前模块对外提供接口
define(function (require, exports, module) {
//依赖encrypt.js,用户名,密码加密
require('headhunt-encrypt');
module.exports = {
Init: function () {
Register.ChangeValidateCode("ImgCode", Register.imgSrc);
Register.TextHanlder();
}
}
});
全局配置需要加载的模块
seajs.config({
// 基础路径
base: 'http://js.cjolimg.com',
// 路径配置
paths: {
"JsCommonHost": "v8/plugins",
},
// 别名配置
alias: {
"jquery": "v8/jquery-1.8.3.min",
"layer": "JsCommonHost/layer/layer",
}
});
// 加载模块
seajs.use(['jquery.js'], function(math){
//
});
/** sea.js **/
// 定义模块 math.js
define(function(require, exports, module) {
var $ = require('jquery.js');
var add = function(a,b){
return a+b;
}
exports.add = add;
});
// 加载模块
seajs.use(['math.js'], function(math){
var sum = math.add(1+2);
});
CommonJs规范最典型的代表:Nodejs,它有四个重要的环境变量为模块化提供支持.module,export,require,global,在实际使用中,使用module.exports定义当前模块对外输出接口,用require来加载模块
commonJS的相关详情,我之前的一篇博客有写,请移步CommonJS规范
// 定义模块math.js
var basicNum = 0;
function add(a, b) {
return a + b;
}
module.exports = { //在这里写上需要向外暴露的函数、变量
add: add,
basicNum: basicNum
}
// 引用自定义的模块时,参数包含路径,可省略.js
var math = require('./math');
math.add(2, 5);
// 引用核心模块时,不需要带路径
var http = require('http');
http.createService(...).listen(3000);
CommonJS规范,对外输出模块的方式有两种
(1) module.exports 当前模块对外输出的接口
(2) exports 每个模块化还提供一个exports变量,指向module.exports
CommonJs采用同步的方式进行加载模块,用于服务端,在服务端,模块文件存储在本地磁盘,读取很快,所以这样不会问他,但是在浏览器中,由于网络等原因,更合理的方法是异步加载
ES6在语言标准层面上,实现了模块功能,意在实现服务器端和浏览器端统一的模块解决方案,ES6的模块系统,主要由2个命令构成,export和import,export规范对外的接口,import引用其他的模块功能,ES6模块不是对象,import命令在编译时引用模块,而不是在运行时.
/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {
return a + b;
};
export { basicNum, add };
/** 引用模块 **/
import { basicNum, add } from './math';
function test(ele) {
ele.textContent = add(99 + basicNum);
}
服务端和浏览器端都适用
默认情况下,浏览器是同步加载JavaScript脚本,即渲染引擎遇到标签就会停下来,等到执行完脚本,再继续渲染,如果是外部脚本,还必须加入脚本下载的时间.如果脚本很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户就会感觉到浏览器
卡死
,没有任何响应
defer要等到整个页面在内存中正常渲染结束(DOM结构完全生成,以及其他脚本执行完成)才会执行
async 一旦下载完,渲染引擎就会中断渲染,执行这个脚本后,再继续渲染
浏览器允许脚本异步加载,有两种异步加载语法
//整个页面解析完了再执行,告诉浏览器,立即下载,延迟执行
<script src="path/to/myModule.js" defer></script>
//下载完就执行,没有顺序
<script src="path/to/myModule.js" async></script>
(1) defer 要等到整个页面在内存中正常渲染结束,才会执行
(2) async 一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后在继续渲染
(3) 如果有多个defer脚本,会按照他们在页面上出现的顺序加载,而多个async脚本是不能保证加载循序的
总结:
defer是渲染完再执行,async是下载完就执行
因为async无法保证加载顺序,所以前后的多脚本,要保证并无依赖关系
浏览器加载ES6模块,也是使用
标签,但是要加入
type='module'
属性
<script type="module" src="./foo.js"></script>
上面代码在网页中插入一个模块foo.js
,由于type
属性设置为module
,所以浏览器知道这是一个ES6
模块.
浏览器对于带有type='module'
的,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了
标签的defer属性.
<script type="module" src="./foo.js"></script>
<!-- 等同于 -->
<script src="./foo.js" defer></script>
如果网页有多个,它们会按照在页面出现的顺序依次执行
<script type="module">
import utils from "./utils.js";
// other code
// ...
</script>
(1) 代码是在模块作用域之中运行,而不是全局作用域运行
(2) 模块内部的顶层变量,外部不可见
(3) 模块脚本自动采取严格模式,不管是否有声明use strict
(4) 模块之中,可以使用import命令来加载其他模块,也可以使用export命令输出对外接口
(5) 模块之中,顶层的this指向,返回undefined,而不是window
(6) 同一个模块如果加载多次,将只执行一次
(1) 都是异步加载模块
(2) 都实现了浏览器端模块化开发的目的
(3) 都倡导模块化开发理念,使前端模块化开发变得简单自然
(4) 解决前端开发过程中两大问题
JS
时,页面失去响应的时间过长(1) 官方推荐的写法不同,AMD
推崇依赖前置,CMD
推崇依赖就近
(2) 模块的执行时机不同,AMD
是提前执行,CMD
是延迟执行(按需加载)
(3) api的设计不同,AMD的API默认是一个当多个用,CMD的API严格区分,推崇职责单一
因AMD
与CMD
类似,所以放一起,而且上面也做了对比
都倡导的是模块化的思想
(1) AMD,CMD
都是用于浏览器的模块化,
(2) CommonJS
是服务端的模块化,
(3) ES6的模块化浏览器和服务端通用
(4) AMD
代表require.js
,CMD
代表sea.js
,CommonJS
代表node.js
(5) AMD
和CMD
都是异步加载,而CommonJS
是同步加载