CommonJS规范
由于ES5没有模块化规范,所以产生了这三种规范。在ES6中又新增了一种公用模块化的方法。
特点:同步
CommonJS规范是通过module.exports定义的,在前端浏览器中并不支持此中规范
浏览器不兼容Common的根本原因也就是缺少四个Node环境的变量
moduleexportsrequireglobal
Node以及Webpack是采用CommonJS的形式来写的
CommonJS定义的模块分为三种:模块引用(require);模块定义(exports);模块标识(module)
require()用来引入外部模块,exports对象用于导出当前模块,或者当前的模块的方法和变量,module对象代表对象本身
var foo = require('foo.js'); var count =1; var plus = ()=>{ foo.add(count); }; module.exports= { count, plus }
特点
对于基本数据类型,和语言本身一样属于复制,即会被模块缓存,在另一个模块中可以对该模块输出的变量重新赋值。
对于复杂数据类型,例如Array,Object,属于浅拷贝,即同时指向一个内存空间,因此对一个模块的值的改变可以影响另一个模块。详见javascript深拷贝与浅拷贝详解。
当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
循环加载
// a.js exports.done = false let b = require('./b.js') console.log('a.js-1', b.done) exports.done = true console.log('a.js-2', '执行完毕') // b.js exports.done = false let a = require('./a.js') console.log('b.js-1', a.done) exports.done = true console.log('b.js-2', '执行完毕') // c.js let a = require('./a.js') let b = require('./b.js') console.log('c.js-1', '执行完毕', a.done, b.done)
运行node c.js
usr:~ usr$ node c.js b.js-1 false b.js-2 执行完毕 a.js-1 true a.js-2 执行完毕 c.js-1 执行完毕 true true
循环加载时,commonjs属于加载时执行,即脚本代码在require时候,就会全部执行。一旦出现某个模块被“循环加载”,只输出已经执行的部分,未执行的部分不输出。
上述代码很明显的表现出了此类特点
step1: node c.js
step2: require(./a.js) //执行完a.js中的内容
step2.1: export.done = flase; let b = require(./b.js); // 执行b中的代码
step2.2: export.done = flase; let a= require(./a.js) //由于发生了循环加载,所以只执行a中的第一句,然后继续执行b.js 输出b.js-1 false b.js-2执行完毕
step2.3: 继续执行b中的代码 将b.done 赋值为true
step 2.4 : 执行a的剩余部分a.js-1 true; a.js-2 执行完毕
step3:require(./b.js) 由于已经执行过了不会再执行b中的代码 所以直接输出b返回值
step4: 输出c.js-1 执行完毕 true true
AMD规范
由于CommonJS的局限性 例如
var math = require('math'); math.add(2,3);
第二行代码必须要在 require之后运行,如果math加载时间很长,就会陷入空等,整个过程是同步的。
对于服务器这不是个问题,因为都存储在本地,但是对于客户端,如果由于网络原因,可能会陷入“假死”状态。
所以客户端,不能采用同步加载,只能使用异步加载。
AMD规范是require.js对模块化定义的一种规范化产出
模块本身和模块之间的引用可以被异步加载
先引入模块后使用模块的方法,称之为依赖前置
优点
1.包括异步调用和本身高扩展性
2.解耦,模块在代码中也可以通过识别号进行查找
define(['./package/lib.js'], function(lib) { function say(){ lib.log('this is fn'); } return { say:say }; });
Tips
lib.js是我们引入的方法,回调中lib参数表示的是引入的模块的所有方法及属性
我们在当前模块定义了say方法,并且return say的执行结果
CMD规范
SeaJs
CMD:同步模块定义
依赖就近原则
sayHello.js
define(function(require,exports,module){ //通过require引入依赖,并不是AMD的依赖前置,而是依赖就近,哪里用,哪里引 //例如下列引入jquery var JQ = require('jquery'); exports.sayHello = function(){ $('#hello').toggle('slow') } })
main.js
define(function(require){ var CMD = require('sayHello'); var temp = new CMD(); temp.sayHello(); })
ES6模块化规范
ES6在语言标准上面实现了模块功能。设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入输出变量。CommonJS以及AMD都只能在运行时确定
ES6的模块并不是对象,而是通过export显示指定输出的代码,再通过import命令导入
//commonJs // CommonJS模块 let { stat, exists, readFile } = require('fs'); // 等同于 let _fs = require('fs'); let stat = _fs.stat; let exists = _fs.exists; let readfile = _fs.readfile; //创建了一个对象 //ES6 import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。
export输出的接口,是变量实时的值,与commonjs输出的是值的缓存不相同
export与import要出现在模块顶层
如果想要重新命名,需要使用as关键字
也可以引入整个模块
import * as bb from './xxx'
也可以匿名导出 这样可以给导出的模块任意指定名字
export default
import anyname from 'xx.js'