记笔记:搞清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.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-从已经存在的模块、脚本文件等导入

到此结束一段落,仅为学习笔记,菜鸟一枚,原理性知识理解不够充分,很多内容都是借鉴而来,如有错误请直接指出,共同进步。

加油吧,打工人!

你可能感兴趣的:(前端知识,javascript,es6,node.js,js)