ES6基础——前端模块化:CommonJS、AMD、CMD、ES6模块

在使用JavaScript开发大型项目时,模块开发是一个必须考虑的问题,流行的js模块化规范有CommonJS、AMD、CMD和ES6模块,通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数。

         一、CommonJS

二、AMD、require.js

三、CMD、Sea.js

四、ES6模块

五、CommonJS与ES6模块的不同

1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

3. CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。


一、CommonJS

在前端浏览器中并不支持该规范,主要用于服务器端,Node.js是CommonJS规范的主要实践者。我们用 module.exports 定义当前模块对外输出的接口(不推荐直接用exports),用 require 加载模块。代码如下:

//定义模块 math.js
const num = 0;
function add(a, b) {
    return a + b;
}
module.exports = {
    add:add,
    num:num
}
//加载模块,参数包含路径,可省略.js
const math = require('/math');
math.add(2, 5);

//引用核心模块时,不需要带路径
const http = require('http');

CommonJS采用同步的方式加载模块,在服务器端,模块文件都存在本地磁盘,读取非常快,因此也不需要异步加载。但在浏览器端的话,则需要浏览器端发起请求到服务端,然后等待服务端返回模块才可以使用,因此应该采用异步加载。

二、AMD、require.js

AMD规范采用异步的方式加载模块,模块的加载不影响它后面语句的执行,主要用于浏览器。所有依赖这个模块的语句都定义在了回调函数中,等到加载完成后,这个回调函数才会运行。require.js实现了AMD规范,用 require.config() 指定引用路径等,用 define() 定义模块,用 require() 加载模块。

//定义 math.js模块
define(function () {
    var num = 0;
    var add = function (x, y) {
        return x + y;
    };
    return {
        add: add,
        num:num
    };
});
//网页中引入文件 require.js 和入口文件 main.js,main.js中配置require.config()
<script src="js/require.js" data-main="js/main"></script>
//入口文件main.js中指定各模块路径和引用名
require.config({
    baseUrl:"js/lib",
    paths: {
        "jquery":"jquery.min", //实际路径 js/lib/jquery.min.js
        "math":
    }
});
//执行基本操作
require(["jquery","math"], function($,_){
   //... 
});

三、CMD、Sea.js

CMD规范采用异步的方式加载模块,sea.js实现了AMD规范,CMD与AMD其实很相似,不同点在于:

  • AMD推崇依赖前置,提前执行,AMD加载完模块后就会执行该模块,所有模块加载执行完后会进入require的回调函数,执行主逻辑,这样会导致依赖模块的执行顺序和书写顺序可能不一致,但主逻辑一定是在所有依赖加载执行后才会执行。因此说AMD用户体验好。
  • CMD推崇依赖就近,延迟执行,CMD加载完某个模块后并不会立即执行,所有模块加载完后进入主逻辑,遇到require语句才执行对应模块,这样模块的执行顺序和书写顺序是一致的。因此说CMD性能好。

四、ES6模块

ES6的Module旨在成为浏览器和服务器通用的模块解决方案,它结合了CommonJS和AMD的优点,语法简洁且支持异步加载,用 export 规定模块的对外接口,用 import 用于加载引用模块。

//定义模块 math.js
const num = 0;
function add(a, b) {
    return a + b;
}
export{ num, add };
<script type="module" src="./foo.js"></script>  //等同于defer属性,渲染完再执行,按顺序加载
<script type="module" src="./foo.js" defer></script>
<script type="module" src="./foo.js" async></script>//打开async属性,下载完就执行,不能保证加载顺序
//加载引用模块
import {num, add} from './math';
function test(x) {
    x.textContent = add(99 + num);
}

//整体加载引用模块
import * as obj from './math';
function test(x) {
    x.textContent = obj.add(99 + obj.num);
}

ES6的模块不是对象,import命令会在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。

五、CommonJS与ES6模块的不同

1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

CommonJS 一旦输出一个值,模块内部的变化就影响不到这个值。而对于ES6模块,JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。

2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

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

3. CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

 

参考资料:

1. 前端模块化:CommonJS,AMD,CMD,ES6

2.(新手总结)前端模块化,AMD,CMD 面试总结

3. Module 的语法-阮一峰

 

 

 

上一篇:打包工具和编译工具区别和作用


下一篇:ES Modules