js基础:require和import到底有什么区别

require

require 是 commonjs 的语法。module.exports 导出一个对象或者基础值。
并且该对象会被缓存在 require.cache。下一次 require 时,会从缓存读取,不会重新执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// module.js
let num = 1;

// 2秒后改变为 22
setTimeout(() => {
num = 22;
}, 200);

module.exports = num;

// index.js
let obj = require('./module1.js');
console.log(obj); // 1
setTimeout(() => {
// 两秒后仍然输出 1
console.log(obj); // 1
}, 2000);

**有一些文章说,commonjs 模块输出的,是值的拷贝,我是不同意的**,浅拷贝,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// module.js
let obj = { a: 1 };

// 2秒后改变为 22
setTimeout(() => {
obj.a = 22;
}, 200);

module.exports = obj;

// index.js
let obj = require('./module1.js');
console.log(obj); // 1
setTimeout(() => {
// 两秒后输出 22
console.log(obj); // 22
}, 2000);

如果真的是一份拷贝值,暴露的值即使是引用类型,也不应该会变。 浅拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// module.js
let obj = { a: 1 };

module.exports = obj;

// index.js
let obj = require('./module.js');

setTimeout(() => {
delete require.cache[require.resolve('./module.js')];
let obj1 = require('../review/module.js');
// 两秒后输出 false
console.log(obj === obj1, 'index'); // false
}, 2000);

如果在外部改变 require 模块暴露的值,模块内是会生效的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// module.js
let obj = { a: 1 };

// 被加上了 b 属性
setTimeout(() => {
console.log(obj, '100');
}, 100);

// 2秒后改变为 22
setTimeout(() => {
obj.a = 22;
}, 200);

module.exports = obj;

// -------------------------

// index.js
let obj = require('./module.js');

obj.b = 1;

setTimeout(() => {
// 两秒后输出 22
console.log(obj, 'index'); // 22
}, 2000);

我们只能说,commonjs 会输出初始执行的结果(不管有没有缓存。即使没缓存,再执行一遍模块,输出结果也是一样的)。如果输出的值包含了引用类型,那更改一样会生效。这是 js 本身的特性,和 commonjs 没什么关系。

除非重新调用 module.exports,去更新模块暴露的值,并且重新执行 require:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let obj = { a: 1 };

setTimeout(() => {
obj.a = 22;
// 重新调用 module.exports
module.exports = 2;
}, 200);
module.exports = obj;

// index.js
let obj = require('./module1.js');
console.log(obj); // { a: 1 }
setTimeout(() => {
// 重新引用
console.log(require('./module1.js')); // 2
}, 2000);

import

import 是 es6 的语法。当使用非 default 暴露时,被引用模块中改变暴露值,引用方可以获取到变化(动态输出绑定)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// module.js
let num = 1;
setTimeout(() => {
num = 22;
}, 200);
export { num };

// index.js
import { num } from './module.js';

// 先输出 1
console.log(num);

setTimeout(() => {
// 两秒后输出 22
console.log(num);
}, 2000);

使用 default 暴露,行为和 commonjs 一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// mod
let num = 1;

setTimeout(() => {
num = 2;
}, 1000);

export default num;
export { num };

// index.js
import mod, { num } from './mod.js';

console.log(mod, num); // 1 1

setTimeout(() => {
console.log(mod, num); // 1 2
}, 2000);

export 语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。这一点与 CommonJS 规范完全不同。CommonJS 模块输出的是值的缓存,不存在动态更新。


对于 CommonJS:

1
2
3
4
5
6
7
8
9
10
11
// module.js
module.exports = { value: 1 };

// main.js
const module = require('./module');
console.log(module.value); // 输出 1
module.value = 2;

// other.js
const moduleAgain = require('./module');
console.log(moduleAgain.value); // 输出 2

这里,module 的导出是共享的引用,更改会影响其他地方。

对于 ES6 模块:

1
2
3
4
5
6
7
8
9
10
11
// module.js
export let value = 1;

// main.js
import { value } from './module';
console.log(value); // 输出 1
value = 2; // 本地修改,会报错。除非 value 是对象,然后改 value 的属性

// other.js
import { value } from './module';
console.log(value); // 输出 1,导出的值不会受外部修改影响

在 ES6 中,导出的值是按引用绑定的,但在导入处不能直接修改它的值。要想修改导出的变量,通常需要在原模块中提供一个函数来进行修改。

  • CommonJS 和 ES6 模块 是两种不同的模块系统,前者是动态的,后者是静态的,前者常用于 Node.js,后者被现代前端开发广泛采用。
  • 导出机制:CommonJS 允许导出对象的动态引用,ES6 模块更偏向于静态分析和编译期优化。
  • 值传递和拷贝:两者都是通过引用导出,类似浅拷贝,但 ES6 模块在导入后不能直接修改导入值,CommonJS 可以直接修改对象。