1.CommonJS
1.1 基本概念
- 为JS的表现来制定规范,因为js没有模块的功能所以CommonJS应运而生,它希望js可以在任何地方运行,不只是浏览器中。
- 同步
- CommonJS规范 通过model.exports定义的,在前端浏览器中并不支持
- NodeJS是CommonJS规范的实现,webpack 也是以CommonJS的形式来书写的。
- CommonJS定义的模块分为: 模块引用(require) ; 模块定义(exports) ; 模块标识(module)
require()用来引入外部模块;exports对象用于导出当前模块的方法或变量,唯一的导出口;module对象就代表模块本身。
var foo = require('foo.js');
var count = 1;
var plusCount = () => {
foo.add(count);
};
module.exports = {
count,
plusCount
};
浏览器不兼容CommonJS的根本原因,也正是在于缺少四个Node.js环境的变量。
- module
- exports
- require
- global
1.2 特点
- 对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值。
- 对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。
// b.js
let num = 1;
let obj = {
name: 'hcd'
};
setTimeout(() => {
console.log('b.js-num:', num);
console.log('b.js-name:', obj.name);
}, 1000)
module.exports = {
num,
obj
}
// a.js
let mod = require('./b.js')
mod.num = 2;
mod.obj.name = 'newName'
console.log('a.js-num:', mod.num);
console.log('a.js-name:', mod.obj.name);
//运行node
node a.js
a.js-num: 2
a.js-name: newName
b.js-num: 1 // 1秒后
b.js-name: newName // 1秒后
- 当使用require命令加载某个模块时,就会运行整个模块的代码。
- 当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
- 循环加载时,属于加载时执行。即脚本代码在require的时候,就会全部执行。一旦出现某个模块被”循环加载”,就只输出已经执行的部分,还未执行的部分不会输出。
// 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
b.js-1 false
b.js-2 执行完毕
a.js-1 true
a.js-2 执行完毕
c.js-1 执行完毕 true true
说明:
- 在Node.js中执行c模块。此时遇到require关键字,执行a.js中所有代码。
- 在a模块中exports之后,通过require引入了b模块,执行b模块的代码。
- 在b模块中exports之后,又require引入了a模块,此时执行a模块的代码。
- 因为 某个模块被”循环加载”,就只输出已经执行的部分,还未执行的部分不会输出。a模块只执行exports.done =
- false这条语句。
- 回到b模块,打印b.js-1, exports, b.js-2。b模块执行完毕
- 回到a模块,接着打印a.js-1, exports, b.js-2。a模块执行完毕
- 回到c模块,接着执行require,需要引入b模块。由于在a模块中已经引入过了,所以不会再执行该模块,而是取到缓存之中的值,直接就可以输出值了。 结束。
2.AMD
2.1诞生背景
基于commonJS规范的nodeJS出来以后,服务端的模块概念已经形成,很自然地,大家就想要客户端模块。而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行。但是,由于一个重大的局限,使得CommonJS规范不适用于浏览器环境。因为会有一个很大的问题:
var math = require('math');
math.add(2, 3);
第二行math.add(2, 3),在第一行require(‘math’)之后运行,因此必须等math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。您会注意到 require 是同步的。
这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于”假死”状态。
因此,浏览器端的模块,不能采用”同步加载”(synchronous),只能采用”异步加载”(asynchronous)。这就是AMD规范诞生的背景。
CommonJS是主要为了JS在后端的表现制定的,他是不适合前端的,AMD(异步模块定义)出现了,它就主要为前端JS的表现制定规范。
2.2AMD
AMD (异步模块定义Asynchronous Module Definition) :是require.js在推广过程中对模块定义的规范化产出
- AMD:
模块本身和模块之间的引用可以被异步的加载,是一个概念
先引入的模块,后使用的引用模块的方法,所以我们称之为依赖前置
- AMD优点:
包括异步的调用和本身的高扩展性,
它实现了解耦,模块在代码中也可通过识别号进行查找。
- require.js
是对AMD这个概念的实现,
require.js的诞生,就是为了解决这两个问题:
实现js文件的异步加载,避免网页失去响应;
管理模块之间的依赖性,便于代码的编写和维护。
- 官网:
http://www.requirejs.cn/
define(['./package/lib.js'], function(lib) {
function say(){
lib.log('this is fn');
}
return {
say:say
};
});
说明:
-
./package/lib.js使我们依赖的模块,回调函数中参数lib表示的是引入的模块的所有方法和属性,我们可以调用
-
后来我们又在当前模块定义了say方法,并return输出
-
可以看到我们是先引入的模块,后使用的引用模块的方法,所以我们称之为依赖前置
3. CMD
Cmd是SeaJs在推广过程中对模块定义的规范化产出
Cmd: 同步模块定义,是一个概念
SeaJs: SeaJS的作者是前淘宝UED,现支付宝前端工程师玉伯。
原则: 依赖就近原则
// 所有的模块通过define定义
define(function (require, exports, module) {
//通过require引入依赖,并不是AMD的前置依赖,而是依赖就近原则,在哪里使用,在哪里引入,就是同步的概念,即用即返回
var $ = require('jquery');
//输出模块中定义的方法
exports.sayHello = function () {
$('#hello').toggle('slow');
};
});
4. AMD和CMD的区别
AMD 通过 require.js实现
CMD 通过 sea.js实现
相同处 :
RequireJS 和 Sea.js 都是模块加载器,倡导模块化开发理念,核心价值是让 JavaScript 的模块化开发变得简单自然。
不同之处 :
- 定位有差异。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。Sea.js则专注于 Web 浏览器端,同时通过 Node 扩展的方式可以很方便跑在 Node 环境中。
- 遵循的规范不同。RequireJS 遵循 AMD(异步模块定义)规范,Sea.js 遵循 CMD(通用模块定义)规范。规范的不同,导致了两者 API 不同。Sea.js 更贴近 CommonJS Modules/1.1 和 Node Modules 规范。
- RequireJS 是依赖前置,Sea.js是依赖就近
- 对开发调试的支持有差异。Sea.js 非常关注代码的开发调试,有 nocache、debug 等用于调试的插件。RequireJS无这方面的明显支持。
- 插件机制不同。RequireJS 采取的是在源码中预留接口的形式,插件类型比较单一。Sea.js 采取的是通用事件机制,插件类型更丰富。
总之,如果说 RequireJS 是 Prototype 类库的话,则 Sea.js 致力于成为 jQuery 类库。
5. ES6
上面的AMD,CMD,CommonJs都是ES5时期的。
ES6中无需引入别的js文件,ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。
我这里只是简单说一下,推荐阮一峰老师的学习网站,里面有非常详细的讲解:
http://es6.ruanyifeng.com/#docs/module
————————————————
版权声明:本文为CSDN博主「南张门」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/haochangdi123/article/details/80408874