一、AMD,异步模块定义(Asynchronous Module Definition)
它是一个在浏览器端模块化开发的规范。
依赖前置(依赖必须是一开始就写好)会尽早执行(依赖)模块,话句话说,所有的require都被提前执行(require可以是全局或者局部)
由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是RequireJS。
1)定义模块:define()
定义一个就modelName的模块,且该模块的依赖为a,b,c。当加载完所有的依赖(即加载完a,b,c)后,再执行回调函数,返回模块的输出值(即对外暴露的值)
define('modelName',['a','b','c'],function(){
return {}//返回模块输出值(由向外暴露的变量组成)
})
- 参数一:定义的
模块名称
,若没有提供该参数,则默认为该模块所在的文件名称(可选) - 参数二:当前
模块的依赖(是数组)
,且数组里依赖的模块必须是已定义的,若没有此参数,默认为['require','exports','module'](可选) - 参数三:模块初始化要执行的函数或对象
2)加载模块:require()
require()函数在加载依赖的函数的时候是异步加载的,这样浏览器不会失去响应,它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题
require(['a','b','c'],function(x,y,z){})
- 参数一:是个数组,表示所依赖的模块
- 参数二:是一个回调函数,参数一的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块(即a与x,b与y,c与z一一对应,我们在回调函数中调用x相当于调用a模块)
二、CMD,通用模块定义(Common Module Definition)
CMD规范是国内发展出来的,就像AMD有个requireJS,CMD有个浏览器的实现SeaJS
在CMD规范中推崇:
- 一个文件一个模块,所以经常就是用文件名作为模块名称
- 依赖就近,所以一般不再define的参数中写依赖,在define的第三个参数函数中写依赖,该函数有三个参数
1)定义模块:define()
CMD推崇依赖就近,实现了懒加载,即什么时候需要什么模块,就require()加载什么模块(按需加载)
define(function(require,exports,module){
var a = require('./a');//按需加载
exports.add = add;//向外暴露的变量
})
- require 是用来获取其他模块提供的接口
- exports是一个对象,用来向外提供模块接口
- module是一个对象,上面存储了与当前模块相关联的 一些属性和方法
CMD和AMD的比较
- AMD:依赖前置,预执行(异步加载:依赖先执行)
- CMD:依赖就近,懒(延迟)执行(运行到需加载,根据执行顺序执行)
- AMD 用户体验好,因为没有延迟,依赖模块提前执行了
CMD性能好,因为只有用户需要的时候执行
AMD依赖前置,js可以方便知道依赖模块是谁,立即加载,而CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了哪些模块,这也是很多人诟病CMD的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到可以忽略。
AMD在加载模块完成后就会执行该模块,所有模块都加载执行完后会进入require的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序与书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但主逻辑一定在所有依赖加载完成后才执行。CMD加载完某个依赖模块后并不执行,只是下载而已
在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块
,这样模块执行的顺序与书写顺序是完全一致的,这也是很多人说AMD用户体验好,因为没有延迟,依赖模块提前执行了,CMD性能好,因为只有用户需要的时候才执行的原因。
三、CommonJS
最早期在网页端没有模块化变成只是页面JavaScript逻辑复杂,但也可以工作下去,在服务器端却一定要有模块,所以CommonJS出现了,CommonJS规范是由NodeJS发扬光大。
1)定义模块:根据CommonJS规范,一个单独的文件就是一个模块,每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非定义为global对象的属性
2)模块输出:模块输出只有一个出口,module.exports对象,我们需要把模块希望输出的内容放入该对象
3)加载模块:加载模块使用require方法,该方法读取一个文件并执行,返回文件内容部的module.exports对象
// 定义模块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,4);
//引用核心模块:参数不需要带路径
var http = require('http');
http.createService(...).listen(3000)
commonJS用同步的方式加载模块,在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题,但是在浏览器端,限于网络原因,更合理的方案是使用异步加载,所以浏览器端一般不使用commonJS了
四、ES6 Module
ES6 Module 主要有两个命令构成:export和import
- export命令:用于规定模块的对外接口
- import命令:用于输入其他模块提供的功能
// 定义模块math.js
var basicNum = 0;
var add = function(a,b){
return a+b;
}
export {basicNum,add};//暴露给外部的变量
// 引用模块
import { basicNum,add } form './math';
function test(ele){
ele.textContent = add(99 + basicNum);
}
如上所示:使用import命令的时候,用户需要知道所要加载的变量名或者函数名。其实ES6还提供了 export default 命令,为模块指定默认输出,对应的import语句不需要使用大括号。
// 定义模块math.js
var basicNum = 0;
var add = function(a,b){
return a+b;
}
export {basicNum,add};//暴露给外部的变量
// 引用模块
import math form './math';
function test(ele){
ele.textContent = math.add(99 + math.basicNum);
}
ES6模块与CommonJS模块
CommonJS模块输出是一个值的拷贝,ES6模块输出的是值的引用。
- CommonJS模块输出的是值得拷贝,也就是说,一旦输出一个值,模块内部的变量影响不到这个值
- ES6模块的运行机制与CommonJS不一样。JS引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行的时候,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6的import原始值变了,import加载的时候值也会跟着变,因此,ES6模块是动态引用,并不会缓存值,模块里面的变量绑定其所在的模块。
CommonJS模块是运行时加载,ES6模块是编辑时输出接口 - 运行时加载:CommonJS模块就是对象,即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上读取方法,这种加载称为‘运行时加载’
- 编译时加载:ES6模块不是对象,而是通过export命令显式指定输出的代码,import时采用静态命令的形式,即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为‘编译时加载’
CommonJS加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而ES6模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
网友评论