欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解搞清CommonJS、AMD、CMD、ES6的联系与区别

程序员文章站 2022-03-22 11:01:28
CommonJS、AMD、CMD、ES6的区别首先我们需要考虑为什么会出现这几种规范?其次我们要考虑模块概念的提出要解决什么问题?然后让我们搞清楚几种规范之间存在的联系和区别  其实,CommonJS规范的提出,主要是为了弥补JavaScript没有标准的缺陷,已达到像Python、Ruby和Java那样具备开发大型应用的基础能力,而不是停留在开发浏览器端小脚本程序的阶段。模块规范主要分为三部分:模块引用、模块定义、模块标识。同时,模块规范很好地解决变量污染问题,每个模块具有独立空间,互不干扰...

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.fn1module.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