cjs 和 esm 引入导出模块的区别

41 阅读3分钟
  1. 引入模块的时候 require 首先读取缓存
// module.js
cnosole.log('running');
module.exports = {
	name1: 'modules1',
  name2: 'modules2'
}

// index.js
const name1 = require('./module.js').name1;
const name2 = require('./module.js').name2;
// "running" 只会打印一次,证明 module.js 内部只执行了一次

而 import 也有缓存

// module.js
console.log('running');
export default {
	name1: 'modules1',
  name2: 'modules2'
}

// index.js
import { name1 } from './module.js';
import { name2 } from './module.js';
💡 其实第一条不算区别,但是还是记录下
  1. require 引入的一个模块,可以使用 ‘use strict’ 来定义该模块是否遵循严格模式

而 import 引入的模块,默认为严格模式,不可以进行切换

  1. require

而 import 引入的变量是只读的

// module.js
console.log('running');

export let name1 = 'modules1';
export let name2 = 'modules2';

// index.js
import { name1 } from './module.js';
import { name2 } from './module.js';

console.log(name1);
name1 = 'my'; // 会报错,提示 Assignment to constant variable
console.log(name1);
  1. cjs 导出只能选择,单个变量导出或者整个模块导出
// module.js
// 以下两种只能选择一种,因为后声明的那种会直接覆盖前一种,例如这里 name1 的值在别的模块取不到
exports.name1 = 'module1';

module.exports = {
  name2: 'module2'
}

而 esm 导出可以两种混用

// module.jss
const name3 = "module3";

export let name1 = 'modules1';
export let name2 = 'modules2';

export default name3;

// index.js
import name3, { name1,name2 } from './module.js';

以上是写法或者用法上的一些区别,接下来看一下本质上的区别

  1. cjs 对于模块依赖的解决是 动态的
// module.js
module.exports = { name: 'name'};

// index.js
const name = require('./module.js').name;

/*
动态体现在:
首先模块引入,其实是将 module.exports 的值作为 require 函数的返回值返回的
这个 require 的传参是可以动态指定的,在运行到这个模块之前,无法判断确定该模块具体是依赖哪个文件
也就是说,模块的导出和引入是发生在代码的运行阶段
*/

而 esm 是 静态的

// module.js
export const name = 'name';

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

/*
静态体现在:
导出模块和引入模块都是声明式的,因此不支持动态语句作为导入路径
并且导出和导入语句必须位于模块作用域的顶层作用域(例如不能放在 if 语句中)

这种声明式的具有一些优点:
1. 死代码检测,通过静态分析工具可以检测没有调用的模块,从而减少产物体积
2. 编译器优化,cjs 本质上导入的还是一个对象,而 esm 直接支持导入变量,从而减少了引用层级
*/
  1. cjs 导入一个对象的时候,本质上获得的是一个导出值的副本
// module.js
let count = 0;
module.exports = {
	count,
  add: function(a, b) {
    count += 1;
    return a+b;
  },
  show: function() {
   return count;
  }
} 

// index.js
let count = require('./module.js').count;
let add = require('./module.js').add;
let show = require('./module.js').show;

console.log(count); // 0  这里读取的初始的副本值
add(2, 3);
console.log(count); // 0 虽然通过闭包我们给 count 值加一,但是读取副本值还是不变的
count += 2;
console.log(count); // 2 副本的值不是只读,可以改变

console.log(show()); // 1 可以通过闭包获取非副本值

而 esm 中获得的是变量的动态映射;

// module.js
let count = 0;
const add = function(a, b) {
  count += 1;
  return a+b; 
}
export {count, add};

// index.js
import { count, add } from './module.js';
console.log(count); // 0
add(2,3);
console.log(count); // 1 实时反映

// count += 1 // 不可修改
  1. cjs 在出现循环依赖的时候,无法读取到依赖的地方,返回的是空对象 {} ,这是因为我们说过 cjs 默认导出的都是一个对象

而 esm 返回的是 undefined,这是因为 esm 默认返回的是一个变量(也可以是对象)

因为某种原因,esm 可以支持循环依赖,但是不建议使用

总结

  1. cjs 可以选择模块是否遵循严格模式,esm默认严格模式
  2. cjs 单独导出和统一导出只能选一种(因为会互相覆盖),esm可以混用
  3. cjs 是动态,路径可以动态设置,esm是静态的,所以可以做死代码检测之类的操作
  4. cjs 引入是一个副本值,esm引入是一个动态映射,但是是只读
  5. cjs 的引入返回值默认是空对象,esm引入默认返回值是 undefined