CommonJS、AMD、CMD、ES6 Modules、ES Harmony

简介: CommonJS、AMD、CMD、ES6 Modules、ES Harmony

模块化

模块化是指把一个复杂的系统分解到多个模块以方便编码。


很久以前,开发网页要通过命名空间的方式来组织代码,例如: jQuery 库把它的API都放在了 window.$ 下,在加载完 jQuery 后其他模块再通过 window.$ 去使用 jQuery。 这样做有很多问题,其中包括:


   命名空间冲突,两个库可能会使用同一个名称

   无法合理地管理项目的依赖和版本

   无法方便地控制依赖的加载顺序


当项目变大时这种方式将变得难以维护,需要用模块化的思想来组织代码。


后面发展起来了众多的前端模块化规范:


20210720141939623.png



CommonJS


定义


   CommonJS 是一个项目,其目标是为 JavaScript 在网页浏览器之外创建模块约定。创建这个项目的主要原因是当时缺乏普遍可接受形式的 JavaScript 脚本模块单元,模块在与运行JavaScript 脚本的常规网页浏览器所提供的不同的环境下可以重复使用。


很长一段时间 JavaScript 语言是没有模块化的概念的,直到 Node.js 的诞生,把 JavaScript 语言带到服务端后,面对文件系统、网络、操作系统等等复杂的业务场景,模块化就变得不可或缺。于是 Node.js 和 CommonJS 规范就相得益彰,共同走入开发者的视线。


CommonJS 是一种使用广泛的 JavaScript 模块化规范,核心思想是通过 require 方法来同步地加载依赖的其他模块,通过 module.exports 导出需要暴露的接口。


   CommonJS 还可以细分为 CommonJS1 和 CommonJS2,区别在于 CommonJS1 只能通过 exports.XX = XX 的方式导出,CommonJS2 在 CommonJS1 的基础上加入了 module.exports = XX 的导出方式。 CommonJS 通常指 CommonJS2。


使用

采用 CommonJS 导入及导出时的代码如下:

// 导入
const moduleA = require('./moduleA');
// 导出
module.exports = moduleA.someFunc;



module.exports 定义当前模块对外输出的接口,用 require 加载模块。

// 定义模块 area.js
function area(radius) {
  return Math.PI * radius * radius;
}
// 在这里写上需要向外暴露的函数、变量
module.exports = { 
  area: area
}
// 引用自定义的模块时,参数包含路径
var math = require('./math');
math.area(2);


优点

  • 代码可复用于 Node.js 环境下并运行,例如做同构应用;
  • 通过 NPM 发布的很多第三方模块都采用了 CommonJS 规范。


缺点

  • 代码无法直接运行在浏览器环境下,必须通过工具转换成标准的 ES5。


这种规范天生就不适用于浏览器,因为它是同步的。浏览器端每加载一个文件,要发网络请求去取,如果网速慢,就非常耗时,浏览器就要一直等 require 返回,就会一直卡在那里,阻塞后面代码的执行,从而阻塞页面渲染,使得页面出现假死状态。


AMD (Asynchronous Module Definition)


定义


AMD 是 “Asynchronous Module Definition” 的缩写,意思就是"异步模块定义"。AMD 规范主要是为了解决针对浏览器环境的模块化问题。


先介绍一下 RequireJS。


RequireJS is a JavaScript file and module loader. It is optimized for in-browser use, but it can be used in other JavaScript environments, like Rhino and Node. Using a modular script loader like RequireJS will improve the speed and quality of your code.


20210720142502657.png


RequireJS 的基本思想是,通过 define 方法,将代码定义为模块。当这个模块被 require 时,它开始加载它依赖的模块,当所有依赖的模块加载完成后,开始执行回调函数,返回值是该模块导出的值。


AMD 就是 RequireJS 在推广过程中对模块定义的规范化产出。AMD 与 CommonJS 最大的不同在于它采用异步的方式去加载依赖的模块。 它解决了 CommonJS 规范不能用于浏览器端的问题。



使用

采用 AMD 导入及导出时的代码如下:

// 定义一个模块
define('module', ['dep'], function(dep) {
  return exports;
});
// 导入和使用
require(['module'], function(module) {
});



优点

  • 可在不转换代码的情况下直接在浏览器中运行;
  • 可异步加载依赖;
  • 可并行加载多个依赖;
  • 代码可运行在浏览器环境和 Node.js 环境下。


缺点

  • JavaScript 运行环境没有原生支持 AMD,需要先导入实现了 AMD 的库后才能正常使用。



CMD (Common Module Definition)


定义


和 AMD 类似,CMD 是 Sea.js 在推广过程中对模块定义的规范化产出。


Sea.js 是阿里的玉伯写的。它的诞生在 RequireJS 之后,玉伯觉得 AMD 规范是异步的,模块的组织形式不够自然和直观。于是他在追求能像 CommonJS 那样的书写形式。于是就有了 CMD 。


Sea.js:追求简单、自然的代码书写和组织方式,具有以下核心特性:


   简单友好的模块定义规范:Sea.js 遵循 CMD 规范,可以像 Node.js 一般书写模块代码。


   自然直观的代码组织方式:依赖的自动加载、配置的简洁清晰,可以让我们更多地享受编码的乐趣。


20210720151507385.png


使用

代码在运行时,首先是不知道依赖的,需要遍历所有的require关键字,找出后面的依赖。具体做法是将function toString后,用正则匹配出require关键字后面的依赖。显然,这是一种牺牲性能来换取更多开发便利的方法。


CMD 规范的实现:

<script src="sea.js"></script>
<script src="a.js"></script>



首先要在 html 文件中引入 sea.js 工具库,就是这个库提供了定义模块、加载模块等功能。它提供了一个全局的 define 函数用来定义模块。所以在引入 sea.js 文件后,再引入的其它文件,都可以使用 define 来定义模块。

// 所有模块都通过 define 来定义
define(function(require, exports, module) {
  // 通过 require 引入依赖
  var a = require('xxx')
  var b = require('yyy')
  // 通过 exports 对外提供接口
  exports.doSomething = ...
  // 或者通过 module.exports 提供整个接口
  module.exports = ...
})
// a.js
define(function(require, exports, module){
    var name = 'morrain'
    var age = 18
    exports.name = name
    exports.getAge = () => age
})
// b.js
define(function(require, exports, module){
    var name = 'lilei'
    var age = 15
    var a = require('a.js')
    console.log(a.name) // 'morrain'
    console.log(a.getAge()) //18
    exports.name = name
    exports.getAge = () => age
})


Sea.js 可以像 CommonsJS 那样同步的形式书写模块代码的秘诀在于:


   当 b.js 模块被 require 时,b.js 加载后,Sea.js 会扫描 b.js 的代码,找到 require 这个关键字,提取所有的依赖项,然后加载,等到依赖的所有模块加载完成后,执行回调函数,此时再执行到 require(‘a.js’) 这行代码时,a.js 已经加载好在内存中了。


优点

  • 同样实现了浏览器端的模块化加载。 可以按需加载,依赖就近。

缺点

  • 依赖 SPM 打包,模块的加载逻辑偏重。



   Sea.js 实现了对 JS 代码的模块化组织,大大提高了前端开发效率。然而在实际项目中,大量的细分模块却导致大量的脚本请求,拖慢了页面加载速度,也给服务器造成不小的压力。针对这一情况,spm(static package manager)因运而生,专门用于打包、压缩 Sea.js 模块以及 CSS 文件。


ES6 Modules


定义

ES6 模块化是欧洲计算机制造联合会 ECMA 提出的 JavaScript 模块化规范,它在语言的层面上实现了模块化。浏览器厂商和  Node.js 都宣布要原生支持该规范。它将逐渐取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。


使用

采用 ES6 模块化导入及导出时的代码如下:

// 导入
import { readFile } from 'fs';
import React from 'react';
// 导出
export function hello() {};
export default {
  // ...
};


优缺点

ES6模块虽然是终极模块化方案,但它的缺点在于目前无法直接运行在大部分 JavaScript 运行环境下,必须通过工具转换成标准的 ES5 后才能正常运行。



ES Harmony


未来的模块


TC39,负责讨论ECMAScript语法和语义定义问题和其未来迭代的标准机构,它是由许多的非常聪明的开发者组成的。这些开发者中的一些人(比如Alex Russell)对Javascript在大规模开发中的用例场景在过去几年一直保持者密切的关注,并且敏锐的意识到了人们对于能够使用其编写更加模块化JS的优良的语言特性的需求。 出于这个原因,目前已经有大量激动人心的,包括在客户端和服务器上都能起作用的弹性模块,一个模块加载器以及更多的对语言的改进提议。


更多请参考:w3cschool:ES Harmony



拓展:Sea.js 与 RequireJS 的异同?


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 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。Sea.js 不强推,采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。


对开发调试的支持有差异。Sea.js 非常关注代码的开发调试,有 nocache、debug 等用于调试的插件。RequireJS 无这方面的明显支持。


插件机制不同。RequireJS 采取的是在源码中预留接口的形式,插件类型比较单一。Sea.js 采取的是通用事件机制,插件类型更丰富。


总之,如果说 RequireJS 是 Prototype 类库的话,则 Sea.js 致力于成为 jQuery 类库。



参考资料


前端科普系列-CommonJS:不是前端却革命了前端

CommonJS、AMD/CMD、ES6 Modules 以及 webpack 原理浅析

深入浅出webpack:前端的发展

RequireJS 中文网

Sea.js 官网

Sea.js 与 RequireJS 的异同

CMD 模块定义规范

w3cschool:ES Harmony









目录
相关文章
|
前端开发
ES6、ES7、ES8、ES9、ES10、ES11新特性2
ES6、ES7、ES8、ES9、ES10、ES11新特性
|
JSON 前端开发 JavaScript
ES6、ES7、ES8、ES9、ES10、ES11新特性3
ES6、ES7、ES8、ES9、ES10、ES11新特性
|
6月前
|
JavaScript 前端开发
CMD和UMD,ES Module的差别
CMD和UMD,ES Module的差别
|
前端开发 JavaScript Java
ES6、ES7、ES8、ES9、ES10、ES11新特性1
ES6、ES7、ES8、ES9、ES10、ES11新特性
|
SQL API Go
保持 Modules 的兼容性(上)
保持 Modules 的兼容性
27 0
|
JSON Go API
保持 Modules 的兼容性(下)
保持 Modules 的兼容性(下)
39 0
|
JavaScript 前端开发
说说你对JavaScript模块化方案的理解和 CommonJS、AMD、CMD、ES6 Module 分别是什么?
模块化是指将一个复杂问题,自顶向下逐层把系统划分为若干模块的过程。对于整个系统来说,这些模块可组合,分解和更换的单元。对于编程领域的模块化就是遵守固定的原则,将一个大文件拆分成多个独立且相互依赖的小模块。简单理解模块化就是在一个js文件中能够引入另一个js文件
139 0
|
JavaScript 前端开发
ES5、ES6和ES2015有什么区别?
ES5、ES6和ES2015有什么区别?
314 0
|
JavaScript 前端开发
模块化开发:CommonJS、AMD 和 ES6 Modules 的区别与使用方式
在前端开发中,模块化开发是一种重要的编程方法,它可以帮助我们更好地组织和管理代码,提高代码的可维护性和复用性。在JavaScript中,有多种模块化开发的标准,包括CommonJS、AMD和ES6 Modules。让我们逐一了解它们的区别和使用方式:
207 0
|
JavaScript 前端开发 编译器
CommonJS与ES6 Module的本质区别
文章主要讨论了CommonJS和ES6 Module两种JavaScript模块系统的核心区别,包括动态与静态解决依赖方式,值拷贝与动态映射,以及如何处理循环依赖的问题。
220 0