在了解AMD,CMD规范前,还是需要先来简单地了解下什么是模块化,模块化开发?模块化是指在解决某一个复杂问题或者一系列的杂糅问题时,依照一种分类的思维把问题进行系统性的分解以之处理。模块化是一种处理复杂系统分解为代码结构更合理,可维护性更高的可管理的模块的方式。可以想象一个巨大的系统代码,被整合优化分割成逻辑性很强的模块时,对于软件是一种何等意义的存在。对于软件行业来说:解耦软件系统的复杂性,使得不管多么大的系统,也可以将管理,开发,维护变得“有理可循”。

模块化理解

1. 什么是模块

  • 将复杂的程序依据一定的规则(规范)拆分成多个模块(文件)
  • 模块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信

2. 模块化的进化过程

  • 全局function : 将不同的功能封装成不同的全局函数

    缺点:虽说可以实现一定的封装效果,但是大量的全局函数,污染全局命名空间,容易引起命名冲突

function module1 () {
//...
}
function module2 () {
//...
}
  • 命名空间 : 简单对象封装

    缺点:减少了全局变量,解决命名冲突,但是外部可以直接修改模块内部的数据

let module = {
data: 'aaa',
func () {
console.log(`${this.data}`)
}
}
module.data = 'bbb' // 直接修改模块内部的数据
module.fn() // bbb
  • IIFE:(自执行函数)

    缺点:实现数据私有, 外部只能通过暴露的方法操作,如果当前这个模块依赖另一个模块怎么办?

  // module.js文件()
(function (window) {
let data = 'aaa'
function func () {
console.log(`${this.data}`)
}
//暴露接口
window.module = { func }
})(window)
// index.html文件
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
module.func() // aaa
console.log(module.data) // undefined 不能访问模块内部数据
module.data = 'bbb' // 不能修改的模块内部数据
module.func() // aaa
</script>
  • IIFE增强 : 引入依赖

  // module.js文件
(function (window, $) {
let data = 'aaa'
function func () {
console.log(`${this.data}`)
}
function func2 () {
$('body').css('background', 'red')
}
//暴露接口
window.module = { func, func2 }
})(window, jQuery)
 // index.html文件
<!-- 引入的js必须有一定顺序 -->
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
module.func2()
</script>

上面引入jQuery库,就把这个库当作参数传入,保证模块的独立性,使得模块之间的依赖关系变得明显。

3. 模块化的作用

通过上面的模块拆分,我们发现:

  • 减少了全局变量,有效的避免了命名污染
  • 更好的分离,按需加载
  • 提高了复用性,维护性

但是比较复杂的应用,模块比较多,难免需要引入多个<script>,这样又会出现其他问题:

  • 请求过多
  • 依赖关系模糊

模块化固然有多个好处,然而一个页面需要引入多个js文件,还得按一定的顺序引入,就可能出现因为引入顺序错误而导致整个项目出现严重问题。而这些问题可以通过模块化规范来解决。

模块化规范

  • CommonJs

CommonJS经node.js应运而生,根据CommonJS规范,每一个模块都是一个单独的作用域。也就是说,在该模块内部定义的变量,无法被其他模块读取。在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。

其核心思想就是一个单独文件就是一个模块,通过require方法来同步加载要依赖的模块,然后通过extportsmodule.exports来导出需要暴露的接口。

// module1.js
var data = 5;
var doSomething = function (value) {
return value + data;
};
// 暴露的接口
module.exports.data = data;
module.exports.doSomething = doSomething;

上面代码通过 module.exports 输出变量 data 和函数 doSomething

var example = require('./module1.js');
console.log(example.data); // 5
console.log(example.doSomething(1)); // 6

require命令用于加载模块文件。require命令的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象。

优点:服务器端模块复用性,NPM中模块包多,有将近20万个。

缺点:加载模块是同步的,只有加载完成后才能执行后面的操作,也就是说现加载现用,不仅加载速度慢,而且还会导致性能、可用性、调试和跨域访问等问题。由于Node.js主要用于服务器编程,加载的模块文件一般都存在本地硬盘,加载起来比较快,不用考虑异步加载的方式,因此,CommonJS规范比较适用。然而,这并不适合在浏览器环境,同步意味着阻塞加载,浏览器资源是异步加载的,鉴于浏览器的情况,为了解决上述同步加载问题,实现异步加载依赖模块,因此有了AMD、CMD解决方案。

  • AMD (Asynchronous Module Definition)

AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。AMD规范是异步加载模块,允许指定回调函数。对于依赖的模块,AMD 推崇提前执行(依赖前置),不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。

其核心接口是:define(id?, dependencies?, factory) ,它要在声明模块的时候指定所有的依赖 dependencies ,并且还要当做形参传到factory 中,对于依赖的模块提前执行,依赖前置。

// a.js (定义没有依赖的模块)
define(function () {
let data = 'aaa'
function doSomething () {
console.log(data)
}
return { doSomething } // 暴露接口
})
// b.js (定义有依赖的模块)
define(['c'], function (c) {
let data = 'bbb'
function doSomething () {
console.log(data + c.getData())
}
return { doSomething } // 暴露接口
})
// c.js (此模块为 b.js 依赖)
define(function () {
let data = 'ccc'
function getData () {
return data
}
return { getData } // 暴露接口
})
// 引入依赖的模块
require(['./a', './b'], function (a, b) { // 依赖必须一开始就写好
a.doSomething()
// ...
b.doSomething()
// ...
})
<body>
<!-- 引入require.js并指定js主文件的入口 -->
<script data-main="./index" src="https://cdn.staticfile.org/require.js/2.3.6/require.min.js"></script>
<script>
setTimeout(() => {
console.log('setTimeout')
}, 0)
</script>
</body>

require()函数在加载依赖的函数的时候是异步加载的,这也是我在这里放了个setTimeout证实一下,这样浏览器不会失去响应,它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。AMD的异步加载解决了阻塞加载、性能问题,模块之间的依赖关系也能清楚的显示出来。

  • CMD (Common Module Definition)

CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。CMD规范和 AMD 很相似,解决同样问题,只是运行机制不同。对于依赖的模块,CMD 推崇延迟执行(依赖就近)

// a.js(定义没有依赖的模块)
define(function (require, exports, module) {
let data = 'aaa'
function doSomething () {
console.log(data)
}
exports.doSomething = doSomething // 暴露接口
})
// b.js (定义有依赖的模块)
define(function (require, exports, module) {
let data = 'bbb'
function doSomething () {
var c = require('./c') // 依赖可以就近书写
console.log(data + c.data)
}
exports.doSomething = doSomething // 暴露接口
})
// c.js (此模块为 b.js 依赖)
define(function (require, exports, module) {
let data = 'ccc'
exports.data = data // 暴露模块
})
// 引入依赖的模块
define(function (require, exports, module) {
//引入依赖模块(异步)
require.async('./a', function (a) {
a.doSomething()
console.log('a是异步的')
})
//引入依赖模块(同步)
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ...
var c = require('./c') // 依赖可以就近书写
console.log(c.data)
// ...
})
<body>
<script src="https://cdn.staticfile.org/seajs/3.0.3/sea.js"></script>
<script>
setTimeout(() => {
console.log('setTimeout')
}, 0)
seajs.use('./index')
</script>
</body>

  • ES6

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。

export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。为了提供方便,不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。

// a.js (定义模块)
var data = 'aaa'
var doSomething = function () {
console.log('log: ' + data)
};
export { data, doSomething } // 引用模块
import { data, doSomething } from './a'

这里在语法不做过多介绍,主要说一说 ES6 模块CommonJS 模块 的差异。

它们有两个重大差异:

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

我们来看看第一个差异,CommonJS模块的加载机制:

// module1.js
var data = 5;
var doSomething = function () {
data++;
};
// 暴露的接口
module.exports.data = data;
module.exports.doSomething = doSomething;
var example = require('./module1.js');
console.log(example.data); // 5
example.doSomething();
console.log(example.data); // 5

ES6 模块的加载机制:

// module1.js
let data = 5;
function doSomething() {
data++;
}
export { data, doSomething }
import { data, doSomething } from './module1';
console.log(data); // 5
doSomething();
console.log(data); // 6

ES6 模块的运行机制与 CommonJS 不一样。ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

总结

  • CommonJS 模块输出的是一个值的拷贝,CommonJS 模块是运行时加载,CommonJS规范主要用于服务端编程,加载模块是同步的,同步意味着阻塞加载,浏览器资源是异步加载的,因此有了AMD、CMD解决方案。
  • AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。AMD规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。AMD 的 API 默认是一个当多个用,对于依赖的模块,AMD 推崇提前执行(依赖前置)
  • CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。CMD 的 API 严格区分,推崇职责单一加载模块是异步的,CMD 推崇延迟执行(依赖就近)。
  • ES6 模块输出的是值的引用,ES6 模块是编译时输出接口,ES6 在语言标准的层面上,实现了模块功能简单,完全可以成为浏览器和服务器通用的模块解决方案。

模块化-CommonJs、AMD、CMD、ES6的更多相关文章

  1. JavaScript模块化CommonJS/AMD/CMD/UMD/ES6Module的区别

    目录 JS-模块化进程 原始的开发方式 CommonJS && node.js AMD && Require.js CMD && Sea.js UMD ...

  2. 前端模块化方案全解(CommonJS/AMD/CMD/ES6)

    模块化的开发方式可以提高代码复用率,方便进行代码的管理.通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数.目前流行的js模块化规范有CommonJS.AMD.CMD以及ES6的模块 ...

  3. 前端模块化小总结—commonJs,AMD,CMD, ES6 的Module

    随着前端快速发展,需要使用javascript处理越来越多的事情,不在局限页面的交互,项目的需求越来越多,更多的逻辑需要在前端完成,这时需要一种新的模式 --模块化编程 模块化的理解:模块化是一种处理 ...

  4. (转) 前端模块化:CommonJS,AMD,CMD,ES6

    模块化的开发方式可以提高代码复用率,方便进行代码的管理.通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数.目前流行的js模块化规范有CommonJS.AMD.CMD以及ES6的模块 ...

  5. CommonJS, AMD, CMD是什么及区别--简单易懂有实例

    CommonJS, AMD, CMD都是JS模块化的规范. CommonJS是服务器端js模块化的规范,NodeJS是这种规范的实现. AMD(异步模块定义)和CMD(通用模块定义)都是浏览器端js模 ...

  6. JS JavaScript模块化(ES Module/CommonJS/AMD/CMD)

    前言 前端开发中,起初只要在script标签中嵌入几十上百行代码就能实现一些基本的交互效果,后来js得到重视,应用也广泛起来了, jQuery,Ajax,Node.Js,MVC,MVVM等的助力也使得 ...

  7. JavaScript模块化演变 CommonJs,AMD, CMD, UMD(一)

    原文链接:https://www.jianshu.com/p/33d53cce8237 原文系列2链接:https://www.jianshu.com/p/ad427d8879cb 前端完全手册: h ...

  8. Javascript模块化编程之CommonJS,AMD,CMD,UMD模块加载规范详解

    JavaSript模块化 在了解AMD,CMD规范前,还是需要先来简单地了解下什么是模块化,模块化开发?     模块化是指在解决某一个复杂问题或者一系列的杂糅问题时,依照一种分类的思维把问 题进行系 ...

  9. 研究一下javascript的模块规范(CommonJs/AMD/CMD)

    最近写react需要使用nodejs作为开发环境,需要通过npm安装一些第三方的依赖库,因此慢慢感觉到nodejs基础薄弱对我带来了一些不安全感,尤其是javascript模块这一块听到了很多概念,比 ...

  10. 插件兼容CommonJS, AMD, CMD 和 原生 JS

    模块标准 CommonJS CommonJS 有三个全局变量 module.exports 和 require.但是由于 AMD 也有 require 这个全局变量,故不使用这个变量来进行检测. 如果 ...

随机推荐

  1. Multipath QUIC (MPQUIC): Design and Evaluation

    "Multipath QUIC: Design and Evaluation" https://multipath-quic.org/conext17-deconinck.pdf ...

  2. mysql CHAR and VARCHAR 比较

    写在前面 面试的时候突然有一位面试官问,说说CHAR和VARCHAR的区别,懵逼了,想自己平常使用的时候直接把VARCHAR拿来就用,真没注意到其中的不同. 反思,为什么没有注意到他们的不同 对于my ...

  3. CSS常见反爬技术

    目录 利用字体 反爬原理 应对措施 难点: 利用背景 反爬原理 应对措施 利用伪类 反爬原理 应对措施 利用元素定位 反爬原理 应对措施 利用字符切割 反爬原理 应对措施 利用字体 反爬原理 反爬原理 ...

  4. linux(centos8):sed命令的应用例子

    一,sed命令的用途 sed是Linux下一款功能强大的非交互流式文本编辑器, 可以对文本文件进行增.删.改.查等操作, 支持按行.按字段.按正则匹配文本内容. 说明:刘宏缔的架构森林是一个专注架构的 ...

  5. 从零开始针对 .NET 应用的 DevOps 运营实践 - Jenkins & SonarQube 安装配置

    一.Overview 继续 DevOps 实施的相关内容,在上一篇的博客中,完成了对于工具链中使用到的软件所需的运行环境的配置,在这一篇的博客中,将聚焦于我们使用到的两个主要的软件:Jenkins 与 ...

  6. Windows Server 2003 Enterprise Edition SP2

    SN: MPQ6X-3MCCF-47H9T-TKC2F-T69WM

  7. spring-boot-route(二十二)实现邮件发送功能

    在项目开发中,除了需要短信验证外,有时候为了节省 短信费也会使用邮件发送.在Spring项目中发送邮件需要封装复杂的消息体,不太方便.而在Spring Boot项目中发送邮件就太简单了,下面一起来看看 ...

  8. 联赛模拟测试24 D. 你相信引力吗 单调栈

    题目描述 分析 因为跨过最大值的区间一定是合法的,所以我们人为地把最大值放在最左边 我们要统计的就是在最大值右边单调不降的序列,可以用单调栈维护 需要特殊处理相同的情况 代码 #include< ...

  9. HashMap 中的哈希值计算问题

    date: 2020-08-21 16:48:00 updated: 2020-08-21 16:52:00 HashMap 中的哈希值计算问题 1. hash 计算 JDK1.8 HashMap源码 ...

  10. mysql幻读、MVCC、间隙锁、意向锁(IX\IS)

    IO即性能 顺序主键写性能很高,由于B+树的结构,主键如果是顺序的,则磁盘页的数据会按顺序填充,减少数据移动,随机主键则可能由于记录移动产生很多io 查询二级索引时,会再根据主键id获取数据页,产生一 ...