详解搞清CommonJS、AMD、CMD、ES6的联系与区别
CommonJS、AMD、CMD、ES6的区别
- 首先我们需要考虑为什么会出现这几种规范?
- 其次我们要考虑模块概念的提出要解决什么问题?
- 然后让我们搞清楚几种规范之间存在的联系和区别
其实,CommonJS规范的提出,主要是为了弥补JavaScript没有标准的缺陷,已达到像Python、Ruby和Java那样具备开发大型应用的基础能力,而不是停留在开发浏览器端小脚本程序的阶段。模块规范主要分为三部分:模块引用、模块定义、模块标识。
同时,模块规范很好地解决变量污染问题,每个模块具有独立空间,互不干扰,命名空间等方案与之相比相形见绌。模块规范支持引入和导出功能,这样可以顺畅地连接各个模块,实现彼此间的依赖关系。
AMD、CMD、ES6规范则继CommonJS规范之后逐渐诞生,因为CommonJS规范更适合服务器端,而其他三种则更好的适用于浏览器端。
正文开始-------
一、JavaScript模块发展历程
- require时代
- 原有写法:模块就是实现特定功能的一组方法,把不同函数(以及记录状态的变量)放到一起,就算一个模块。
function m1(){ //... } function m2(){ //... }
这种做法的缺点:”污染“了全局变量,无法保证不与其他模块发生命名冲突,而且模块之间并看不出直接关系。
- 对象写法
为了解决上面的缺点,把模块写成一个对象,所有模块成员都放到这个对象里:
var module = new Object({
name:'xiaoming',
fn1:function(){
//...
},
fn2:function(){
//...
}
})
当我们使用时候则直接调用对象的属性:module.fn1
、module.name
…
但是这种写法会暴露所有模块成员,外部可以直接改写内部变量:module.name = 'honghong'
…
在ES6(ECMAScript 6)之前,还没有一套官方的规范,通行的JavaScript模块规范化有两种:CommonJS和AMD注:ECMAScript 和 JavaScript 的关系:前者是后者的语法规格,后者是前者的一种实现
CommonJS规范
2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。
这标志”Javascript模块化编程”正式诞生。前端的复杂程度有限,没有模块也是可以的,但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。
node编程中最重要的思想之一就是模块,而正是这个思想,让JavaScript的大规模工程成为可能。模块化编程在js界流行,也是基于此,随后在浏览器端,requirejs和seajs之类的工具包也出现了,可以说在对应规范下,require统治了ES6之前的所有模块化编程,即使现在,在ES6 module被完全实现之前,还是这样。
在CommonJS中,暴露模块使用module.exports和exports,有一个全局性方法require(),用于加载模块。假定有一个数学模块math.js,就可以像下面这样加载。
var math = require("math")
//调用
math.addNum(1,5) // 6
因为CommonJS的推动,才有了后面的AMD、CMD也采用require引用模块的风格。
AMD规范
由于CommonJS奠定了服务器模块规范,大家便开始考虑客户端模块,而且想两者可以兼容,一个模块可以同时在服务器和浏览器运行。
但是CommonJS是同步加载模块,服务器所有模块都存放在本地,硬盘读取时间很快,但是对于浏览器来说,等待时间则取决于网速的快慢,如果时间过长,浏览器则处于”假死“状态。例如如上代码,当我们调用`math.addNum(1,5),浏览器只能等待math.js加载完成后才能进行计算,所以浏览器端模块化不能使用同步加载,需用异步加载取代之,这也就是AMD规范诞生的背景。
AMD是”Asynchronous Module Definition”的缩写,意思就是”异步模块定义”。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
- 定义模块
define(id?,dependencies?,factory)
//id:字符串,模块名称(可选)
//dependencies:要载入的依赖模块(可选)
//factiry:工厂方法:返回一个模块函数
当然,如果一个模块不依赖于其他模块,那么可以直接定义在define()中
//main.js
define(function (){
var add = function(a,b){
return a+b;
}
return add
})
如果这个模块依赖于其他模块,那它第一个函数需要是一个数组,指明它以来的模块:
//main.js
define(['module1'],function (module1){
var foo = function(){
module1.doSomething()
}
return foo
})
当我们require()模块时,就会预先加载module1模块
- require加载模块:它需要两个参数(区别于CommonJS))
require([module],callback())
举个栗子:
require(['math'], function (math) {
math.add(2, 3);
});
math.add()和math模块异步加载,不会让浏览器发生假死现象,相对CommonJS,它更适合于浏览器。
CMD规范
CMD (Common Module Definition), 是seajs推崇的规范,CMD则是依赖就近,用的时候再require。它写起来是这样的:
define(function(require, exports, module) {
var clock = require('clock');
clock.start();
});
CMD与AMD一样,也是采用特定的define()函数来定义,用require方式来引用模块
define(id?, dependencies?, factory)
//id:字符串,模块名称(可选)
//dependencies: 是我们要载入的依赖模块(可选),使用相对路径。,注意是数组格式
//factory: 工厂方法,返回一个模块函数
define('hello', ['jquery'], function(require, exports, module) {
// 模块代码
});
如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。
define(function(require, exports, module) {
// 模块代码
});
- CMD与AMD区别
AMD和CMD最大的区别是对依赖模块的执行时机处理不同,而不是加载的时机或者方式不同,二者皆为异步加载模块。
AMD依赖前置,js可以方便知道依赖模块是谁,立即加载;
而CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是很多人诟病CMD的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到可以忽略。
Es6 Modules
Es6标准发布后,成为module标准:使用export指令导出接口,import引入模块,但是在node模块中,我们依然使用CommonJS规范,(require引入模块,module.exports导出接口)
- export导出模块
export 用于导出函数、对象、指定文件(或模块)的原始值。
export有两种导出模块方式:命名式导出(名称导出)和默认导出(定义式导出),命名式导出每个模块可以导出多个,默认导出则仅一个。
export { name1, name2, …, nameN };
export { variable1 as name1, variable2 as name2, …, nameN };
export let name1, name2, …, nameN; // also var
export let name1 = …, name2 = …, …, nameN; // also var, const
export default expression;
export default function (…) { … } // also class, function*
export default function name1(…) { … } // also class, function*
export { name1 as default, … };
export * from …;
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from...
- name1… nameN-导出的“标识符”。导出后,可以通过这个“标识符”在另一个模块中使用import引用
- default-设置模块的默认导出。设置后import不通过“标识符”而直接引用默认导入
- *-继承模块并导出继承模块所有的方法和属性
- as-重命名导出“标识符”
- from-从已经存在的模块、脚本文件…导出
命名式导出
- 模块可以通过export前缀关键词声明导出对象,导出对象可以是多个。这些导出对象用名称进行区分,称之为命名式导出。
export { myFunction }; // 导出一个已定义的函数
export const foo = Math.sqrt(2); // 导出一个常量
- 使用*和from实现模块继承
export * from 'article
- 模块导出时,可以指定模块的导出成员。导出成员可以认为是类中的公有对象,而非导出成员可以认为是类中的私有对象:
var name = 'IT笔录';
var domain = 'http://itbilu.com';
export {name, domain}; // 相当于导出
{name:name,domain:domain}
模块导出时,我们可以使用as关键字对导出成员进行重命名:
var name = 'IT笔录';
var domain = 'http://itbilu.com';
export {name as siteName, domain};
注意,下面的语法有严重错误的情况:
// 错误演示
export 1; // 绝对不可以
var a = 100;
export a;
export a 看上去没有问题,但是a是一个数字,无法进行解构,所以必须写成export {a}
形式,即使a赋予的是一个function,也不允许
- 默认导出
默认导出也叫定义式导出,命名式导出可以导出多个值,但是引用时也要用相应值去对应,而默认导出每个模块导出只有一个单一值,这样模块import引用时也更简单。
export default function(){}
export default class(){}
注意 以下两种形式导出等价
const Aa = 'javaScript'
export default Aa
export {Aa as default}
- 使用示例
命名式导出
定义
//module1.js
export function fn1(a,b){
return a+b
}
const num = 123
export {num}
引用:
import {fn1,num} from 'module1'
- 默认导出
定义:
//module2.js
export default function(){
console.log('默认导出')
}
引用:
import fn2 from 'module2'
fn2(); // 默认导出
- import引入
import语法声明用于从已导出的模块、脚本中导入函数、对象、指定文件(或模块)的原始值,与export导出功能相对应,也存在两种模块导入方式:命名式导入和默认导入。
import导入和require有差异,import必须放在文件最开始,且前方不允许有任何逻辑代码。
示例:
import defaultMember from "module-name";
import * as name from "module-name";
import { member } from "module-name";
import { member as alias } from "module-name";
import { member1 , member2 } from "module-name";
import { member1 , member2 as alias2 , [...] } from "module-name";
import defaultMember, { member [ , [...] ] } from "module-name";
import defaultMember, * as name from "module-name";
import "module-name";
- name-从将要导入模块中收到的导出值的名称
- member, memberN-从导出模块,导入指定名称的多个成员
- defaultMember-从导出模块,导入默认导出成员
- alias, aliasN-别名,对指定导入成员进行的重命名
- module-name-要导入的模块。是一个文件名
- as-重命名导入成员名称(“标识符”)
- from-从已经存在的模块、脚本文件等导入
到此结束一段落,仅为学习笔记,菜鸟一枚,原理性知识理解不够充分,很多内容都是借鉴而来,如有错误请直接指出,共同进步。
加油吧,打工人!
本文地址:https://blog.csdn.net/hanyanshuo/article/details/110134788
上一篇: HashMap系列之1.8put源码篇
下一篇: 学习Java基础入门的笔记