CommonJS 模块与 ES 模块有三个重大的差异:
- CommonJS 模块输出的是一个值的拷贝(浅拷贝),ES 模块输出的是值的引用。
👆 加载 CommonJS 模块会在内存里产生缓存,当再次引用该 CommonJS 模块时会直接从缓存中获取,这会导致拿到的模块不是最新的,即当被引用的模块内部改变自身导出的值时,在引用他的模块中得不到最新的值。而 ES 模块输出的是引用,所以 ES 模块会顺着该引用得到最新的值。
-
CommonJS 模块是运行时加载,ES 模块是编译时输出接口。
-
CommonJS 模块的
require()
是同步加载模块,ES 模块的import
命令是异步加载,有一个独立的模块依赖的解析阶段。
👆 由于 CommonJS 模块的
require()
是同步加载的,这导致他不适合于浏览器,因为同步的任务会阻塞浏览器的渲染。由于服务器的资源都在本地,同步加载起来很快,因此 CommonJS 模块可以在服务器中使用。而 ES 模块的import
命令是异步加载的,不会阻塞浏览器的渲染,因此适合在浏览器中使用,当然在服务器中使用也没问题,这样能统一浏览器和服务器模块化方案。
require
加载 CommonJS 模块时,会先在缓存中查找是否有该模块,如果在缓存中有该模块,则会直接返回缓存中的该模块,而不会走加载模块的逻辑,如果在缓存中不存在该模块,则会走真正的模块加载流程。由于 require
加载的模块存在缓存的缘故,当加载的模块内部动态修改了自身的值的时候,外部的模块得到的值并不是最新的。如下面的例子:
模块 c1 导出了变量 foo
,并且值为 bar
。500 毫秒后,将变量 foo
的值设置为 baz
,然后打印 foo
变量的值。
// c1.js
var foo = "bar";
setTimeout(() => (foo = "baz"), 500);
module.exports.foo = foo;
setTimeout(() => {
console.log("c1 == ", foo);
}, 500);
c2 模块中使用 require
加载 c1 模块,然后输出 foo
变量,并在 1500 毫秒后重新输出 foo
。
// c2.js
var { foo } = require("./c1.js");
console.log(foo);
setTimeout(() => console.log(foo), 1500);
在终端运行 c2 模块,发现虽然在 c1 内部 foo
变量的值已经变为 baz
了,但是外部引入他的模块得到的还是旧值 bar
。
如果要得到 CommonJS 模块内最新的值,可以通过 导出函数
,在该函数返回模块内部的值,然后调用该函数获取模块内部最新的值。如下面的例子:
在 c1 模块中定义值为 bar
的 foo
变量,并导出 getFoo
函数,该函数用于返回 foo
变量
// c1.js
var foo = "bar";
setTimeout(() => (foo = "baz"), 500);
// 导出函数
module.exports.getFoo = function () {
return foo;
};
setTimeout(() => {
console.log("c1 == ", foo);
}, 500);
c2 模块导入 getFoo
函数,并调用 getFoo
函数,输出 getFoo
函数的返回值。为了验证 getFoo
函数能否得到模块内部最新的值,延迟 1500 毫秒后再次输出调用 getFoo
函数得到的返回值。
// c2.js
var { getFoo } = require("./c1.js");
console.log(getFoo());
setTimeout(() => {
console.log(getFoo());
}, 1500);
在终端运行 c2 模块,可以发现导出函数,通过该函数能获得模块内最新的值。
ES 模块的加载机制和 CommonJS 模块的加载机制不一样。ES 模块不会缓存模块运行结果,而是动态地去被加载的模块取值。因此当被加载的模块的值更新后,外部依赖他的模块总是能得到最新的值。如下面的例子:
m1 模块导出了变量 foo
,值为 bar
,并在 500 毫秒后将变量 foo
值设为 baz
。
// m1.js
export var foo = "bar";
setTimeout(() => (foo = "baz"), 500);
m2 模块导入了 m1 模块的 foo
变量,并输出 foo
变量 ,并在 500 毫秒后重新输出 foo
变量。
// m2.js
import { foo } from "./m1.js";
console.log(foo);
setTimeout(() => console.log(foo), 500);
在终端执行 m2 模块:
可以看到,m2 中可以得到 m1 模块 foo
的最新值 baz
总结
关于CommonJS 模块与 ES 模块的区别,记住下面的三句话:
-
CommonJS 模块输出的是值的拷贝(浅拷贝),ES 模块输出的是值的引用。当模块内部的值改变时,在外部,CommonJS 无法得到模块内部最新的值,而 ES 模块可以得到模块内部最新的值。
-
CommonJS 模块是运行时加载,ES 模块是编译时输出接口,ES 模块的加载效率比 CommonJS 模块的高。
-
CommonJS 模块的
require()
是同步加载模块,同步会阻塞后面的代码执行,所以 CommonJS 模块化规范适合在服务器端使用,不适合在浏览器中使用,ES 模块的import
命令会异步加载模块,同时适合在浏览器和服务器中使用。
掘友们,加油~
原文:https://juejin.cn/post/7374238289106845735