ESM(ES6)和CJS(CommonJS)的对比(import、export、exports、require、define)

前言

ESM(ECMAScript Modules)和 CJS(CommonJS)是 JavaScript 的两种模块系统:

  • CJS(CommonJS)

    • Node.js 传统模块系统。
    • 使用 require() 导入,module.exports 导出。
    • 同步加载,适合服务器端。
    • 动态加载(可在条件语句中使用)。
  • ESM(ECMAScript Modules)

    • JavaScript 官方标准(ES6+)。
    • 使用 import / export。
    • 静态结构(必须在顶层),支持 tree-shaking。
    • 浏览器原生支持,Node.js 需启用(如设 “type”: “module”)。
    • 导出的是实时只读绑定,非值拷贝。

简记:

CJS 是 Node 旧标准,动态同步;ESM 是现代标准,静态异步,更适合前端和NodeJS以后的开发。

Web前端推荐使用ESM。

NodeJS推荐使用ESM或CJS。

ESM

在ES6中,我们可以使用 import 关键字引入模块,通过 exprot 关键字导出模块,功能较之于前几个方案更为强大,也是我们所推崇的。

导出/导入

导出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export const A = 42;

export const api = {};

export function myfunc() {};

export class MyClass {};

var myapi = {}; export {myapi};

var m1 = 1; var m2 = 2; export {m1, m2};

function bb(){

}
export {bb};


// 输出指定变量,并重命名,则外部引入时得到的是as后的名称。
var n = 1;
export {n as m};

导入

导入变量/方法的时候变量和方法名必须和导出时一致

1
2
3
import { A,api,myfunc,myapi,MyClass } from './A'
// 导入设置别名
import { A as AA, myfunc as afunc } from './A'

import导入的变量都是只读的,加载后不能修改

1
2
import { m } from 'my_module';
m = 1; // SyntaxError: "m" is read-only

默认-导出/导入

注意default一个文件只能有一个

导出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export default "http://www.psvmc.cn";

export default {}; //Vue组件化常用方式

let myapi = {}; export { myapi as default };
// 这种和上面的方式一样
export default myapi;

// 导出方法的时候写不写方法名都可以
export default function() {};
export default function MyFunc() {};

// 导出类的时候写不写类名都可以
export default class { }
export default class MyClass{ }
class Person{}; export default Person;

导入
导入默认时变量和方法名可自定义

1
2
3
import A from './A'
// 导入设置别名
import { default as myApi } from './A';

全部导出/导入

全部导出

1
export * from "module"

export * from "module"不包含default导出,但export * as ns from "module"(在ns上)是包含default导出的。

全局导入

1
2
let m1 = 1; let m2 = 2; export {m1, m2};
export default 3;

导入

1
2
3
4
import * as mynum from '@/assets/js/Test'
console.info(mynum.m1);
console.info(mynum.m2);
console.info(mynum.default);

注意

导出的默认不能直接通过console.info(mynum);打印。

直接打印的化结果如下:

image-20220726163942294

页面中使用

报错

Uncaught SyntaxError: Cannot use import statement outside a module

方式1(不建议 学习的时候可以这样)

HTML中直接用要添加type="module"

1
2
3
4
<script type="module">
import { zj } from "./index.js";
console.info(zj);
</script>

方式2 通过webpack打包

但是由于ES6目前无法在浏览器中执行,所以,我们只能通过babel将不被支持的import编译为当前受到广泛支持的 require

解决方法:在项目中配置webpack即可

CommonJS(NodeJS)

前端浏览器不支持,用于服务器,Nodejs中使用的是这个规范

导出

1
2
3
exports.area = function(r) {
return Math.PI * r * r;
}

CommonJS的核心思想就是通过 require 方法来同步加载所要依赖的其他模块,然后通过 exports 或者 module.exports 来导出需要暴露的接口。

导入

1
const { area } = require("area");

AMD/CMD

AMD

浏览器端的模块,不能采用后端使用的CommonJS的”同步加载”(synchronous),只能采用”异步加载”(asynchronous),这就是AMD规范诞生的背景。

AMD是RequireJS在推广过程中对模块定义的规范化产出。
AMD规范则是非同步加载模块,允许指定回调函数。

AMD标准中,定义了下面两个API:

  • require([module], callback)
  • define(id, [depends], callback)
    即通过define来定义一个模块,然后使用require来加载一个模块。

require还支持CommonJS的模块导出方式。

test.js

1
2
3
4
5
6
7
8
9
10
11
12
13
define(['package/lib',...], function(lib) {
function foo () {
lib.log('hello world');
}

return {
foo: foo
}
});

require(['test'], function(test) {
test.foo()
})

CMD

CMD是SeaJS在推广过程中对模块定义的规范化产出。

CMD是同步模块定义。

1
2
3
4
5
6
7
8
//所有模块都通过define来定义
define(function(require, exports, module) {
// 通过require引入依赖
var $ = require('jquery');
var Spinning = require('./spinning');
exports.doSomething = ...
module.exports = ...
})

二者的区别是前者是对于依赖的模块提前执行,而后者是延迟执行。

前者推崇依赖前置,而后者推崇依赖就近,即只在需要用到某个模块的时候再require。