1. 遵循的规范不同
- require/exports是CommonJS的一部分;
- import/export是ES6新规范
2. 加载机制
2.1 CommonJS 模块的加载原理
- CommonJS 的一个模块,就是一个脚本文件;
- require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象;
- 该对象的id属性是模块名,exports属性是模块输出的各个接口,loaded属性是一个布尔值,表示该模块的脚本是否执行完毕;
以后需要用到这个模块的时候,就会到exports属性上面取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。也就是说,CommonJS 模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
{id: '...',exports: { ... },loaded: true,...}
2.2 import
代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
- 模块脚本自动采用严格模式,不管有没有声明 use strict。
3. 本质上的不同
3.1 CommonJS 模块是运行时加载,ES6 模块是编译时输出接口
因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。
而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
3.2 CommonJS 模块输出的是一个值的拷贝(相当于浅拷贝),ES6 模块输出的是值的引用
3.2.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)
2. **import**: 同上```javascript// im_test_01.mjslet num = 1setTimeout(() => {num = 10}, 2000)export default num// im_test_02.mjsimport num from './test_01.mjs'console.log(num) // 1// num = 123 // TypeError: Assignment to constant variable.setTimeout(() => {console.log(num); // 1}, 3000)
总结:对于基本数据类型,在原模块修改其值,引用模块擦觉不到变化;
3.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)
2. **import:**同上;```javascript// im_test_01.jslet obj = {num: 1}obj.num++}, 2000)export default obj// im_test_02.jsimport obj from './test_03.mjs'console.log(obj) // { num: 1 }obj.num = 5setTimeout(() => {console.log(obj); // { num: 6 }}, 3000)
3.2.3 通过原模块修改值后
- 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 执行修改方法后
2. **import:**通过调用方法,修改了原模块的数据;```javascript// im_test_01.jsexport let counter = 3;export function incCounter() {counter++;}// im_test_02.jsimport { counter, incCounter } from './test_04.mjs'console.log(counter); // 3incCounter()console.log(counter, '执行修改方法后'); // 4
3.2.4 思考
- 如果说 require 是值的 copy,相当于浅拷贝,但是对于单层复杂类型数据就开始拷贝?
- 如果说 es6 是值的引用,那么当在原模块中通过 setTimeout 修改简单类型数据的值后,引用模块的数据为什么没有出现同步变化?
- 通过 3.2.3 对比发现,import 在自身修改,和通过在引用模块修改数据后,前者的数据在引用模块没有变化,而后者确实出现了变化,这是因为什么?
