1. 遵循的规范不同

  1. require/exports是CommonJS的一部分;
  2. import/export是ES6新规范

2. 加载机制

2.1 CommonJS 模块的加载原理

  1. CommonJS 的一个模块,就是一个脚本文件;
  2. require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象;
  3. 该对象的id属性是模块名,exports属性是模块输出的各个接口,loaded属性是一个布尔值,表示该模块的脚本是否执行完毕;
  4. 以后需要用到这个模块的时候,就会到exports属性上面取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。也就是说,CommonJS 模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。

    1. {
    2. id: '...',
    3. exports: { ... },
    4. loaded: true,
    5. ...
    6. }

    2.2 import

  5. 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。

  6. 模块脚本自动采用严格模式,不管有没有声明 use strict。

3. 本质上的不同

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

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

3.2 CommonJS 模块输出的是一个值的拷贝(相当于浅拷贝),ES6 模块输出的是值的引用

3.2.1 引用基本数据类型

  1. require: 基本数据类型的引用,在原模块修改后,不会同步到新的模块;且新模块不可直接修改其值,相当于 const 定义的常量 ```javascript // test_01.js let num = 1 setTimeout(() => { num++ }, 2000) module.exports.num = num

// test_02.js const { num } = require(‘./test_01’) console.log(num) // 1 // num = 123 // TypeError: Assignment to constant variable. setTimeout(() => { console.log(num); // 1 }, 3000)

  1. 2. **import**: 同上
  2. ```javascript
  3. // im_test_01.mjs
  4. let num = 1
  5. setTimeout(() => {
  6. num = 10
  7. }, 2000)
  8. export default num
  9. // im_test_02.mjs
  10. import num from './test_01.mjs'
  11. console.log(num) // 1
  12. // num = 123 // TypeError: Assignment to constant variable.
  13. setTimeout(() => {
  14. console.log(num); // 1
  15. }, 3000)
  1. 总结:对于基本数据类型,在原模块修改其值,引用模块擦觉不到变化;

    3.2.2 引用复杂数据类型

  2. require: 对于对象的引用,可以修改其子属性,同时子属性的变化,会同步到原模块,可以看出它们操作的是同一个对象; ```javascript // test_01.js let obj = { num: 1 } setTimeout(() => { obj.num++ }, 2000) module.exports = obj

// test_02.js const obj = require(‘./test_01’) console.log(obj) // { num: 1 } obj.num = 5 setTimeout(() => { console.log(obj); // { num: 6 } }, 3000)

  1. 2. **import:**同上;
  2. ```javascript
  3. // im_test_01.js
  4. let obj = {
  5. num: 1
  6. }
  7. obj.num++
  8. }, 2000)
  9. export default obj
  10. // im_test_02.js
  11. import obj from './test_03.mjs'
  12. console.log(obj) // { num: 1 }
  13. obj.num = 5
  14. setTimeout(() => {
  15. console.log(obj); // { num: 6 }
  16. }, 3000)

3.2.3 通过原模块修改值后

  1. require:执行修改方法后,原模块的数据没有变化 ```javascript // test_01.js var counter = 3; function incCounter() { counter++; } module.exports = { counter: counter, incCounter: incCounter, };

// test_02.js const countobj = require(‘./test_04’) console.log(countobj.counter); // 3 countobj.incCounter() console.log(countobj.counter, ‘执行修改方法后’); // 3 执行修改方法后

  1. 2. **import:**通过调用方法,修改了原模块的数据;
  2. ```javascript
  3. // im_test_01.js
  4. export let counter = 3;
  5. export function incCounter() {
  6. counter++;
  7. }
  8. // im_test_02.js
  9. import { counter, incCounter } from './test_04.mjs'
  10. console.log(counter); // 3
  11. incCounter()
  12. console.log(counter, '执行修改方法后'); // 4

3.2.4 思考

  1. 如果说 require 是值的 copy,相当于浅拷贝,但是对于单层复杂类型数据就开始拷贝?
  2. 如果说 es6 是值的引用,那么当在原模块中通过 setTimeout 修改简单类型数据的值后,引用模块的数据为什么没有出现同步变化?
  3. 通过 3.2.3 对比发现,import 在自身修改,和通过在引用模块修改数据后,前者的数据在引用模块没有变化,而后者确实出现了变化,这是因为什么?

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