更好的前端模块化: ESModule6 和CommonJS - 原理区别

349 阅读2分钟

两种JS脚本 ESM(新)和CJS(旧)

  • CJS使用require()、module.exports;
  • ESM使用import、export。
  • node中默认支持CJS语法 require
  • ESM可以调用CJS脚本 但是只能默认引入 默认导出的CJS脚本
// CJS需要默认导出
export.export = 1

// ESM默认引入
import NUM from './cjs.js'

// 不能使用如下写法!!!!!
export.export = { num:1,age:18 }
import {num,age} from './cjs.js'

完全不同的ESModule和CommonJS

  1. CJS使用require()进行脚本引入 他会同步引用并执行模块代码 当JS脚本遇到require时会立即运行其引用的JS文件
const getName = require("./getName.js")

const name = getName({id:1}) // 执行到这里时立刻调用require(),并加载内部的脚本
  1. ESM会异步加载模块

(1)首先会解析脚本中的import和export等关键字 构建一个文件依赖图 (类似webpack的构建过程)

(2)然后会异步下载这些所有的依赖模块

(3)最后 ESM 会进入执行阶段,按顺序执行各模块脚本。

所以我们常常会说,CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

ESM会更改JS中的内容

  1. 首先ESM会默认使用严格模式 this不引用全局对象 作用域的工作方式也不同…
  2. JS脚本现在的默认设置依然是CJS
//所以需要在script标签中加上type='module' 才能支持ESM  
<script type='module' src="..."/>

Node默认使用CJS 而Deno默认使用ESM 这导致了Deno的生态需要从零开始

ESM 无法使用 __filename 和 __dirname

这样一来在 Node.js 中,如果要把默认的 CJS 切换到 ESM,会存在巨大的兼容性问题

CommonJS模块输出是一个值的拷贝,ES6模块输出的值是引用

如何理解这句话呢

  1. 在Node中,所有的模块都会生成并且暴露出module.export对象, 我们引入的是module.export中暴露的变量,而不是原文件中的变量.

image.png

上图蓝色的num共用同一个引用,都指向module.export中的num(index,js中num的拷贝),

下图橙色的num直接指向index中的num

由上图可推出 在ESM中 源文件和引用的模块中的num会相互影响
在CJS中则不会 (因为中间多了一层module对象)