基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

ES6 模块(import/export)和 CommonJS(require/module.exports)的区别

知识点图片

ES6 模块(ESM)和 CommonJS(CJS)是 JavaScript 中两种截然不同的模块化规范。它们不仅在语法上有区别,在加载机制、导出值的形式、以及执行时机上都有着根本的不同。

以下是它们的核心区别详解:


1. 核心区别对比表(太长不看版)

特性 ES6 模块 (ESM) CommonJS (CJS)
标准来源 ECMAScript 官方语言标准 Node.js 社区标准
语法 import / export require / module.exports
加载方式 异步加载(也支持同步),适合浏览器 同步加载,适合服务器端 (Node.js)
解析时机 编译时输出(静态解析) 运行时加载(动态解析)
导出形式 导出的是值的动态只读引用 导出的是值的拷贝(浅拷贝)
顶层 this undefined 指向当前模块对象 (module.exports)
Tree-Shaking 原生支持(因为是静态的) 不支持(打包工具需额外配置或无法做到完美)

2. 详细原理解析

① 导出值的形式:值的拷贝 vs 动态引用(最常考点)

  • CommonJS 输出的是值的拷贝
    一旦输出了某个值(特别是基本数据类型),模块内部发生的变化不会影响到已经输出的值。

    javascript
    // counter.js (CommonJS)
    let count = 1;
    function add() { count++; }
    module.exports = { count, add };
    
    // main.js
    const { count, add } = require('./counter.js');
    console.log(count); // 1
    add();
    console.log(count); // 1 (依然是1,内部改变不影响外部)
  • ES6 Module 输出的是值的动态只读引用
    ES6 模块不会缓存运行结果,而是动态地去被加载的模块里取值。模块里面的变量绑定着所在的模块,且外部引用的变量是只读的(不能重新赋值)。

    javascript
    // counter.js (ES6)
    export let count = 1;
    export function add() { count++; }
    
    // main.js
    import { count, add } from './counter.js';
    console.log(count); // 1
    add();
    console.log(count); // 2 (动态引用,值跟着变了)

② 解析时机:静态分析 vs 动态运行时

  • CommonJS 是动态的(运行时加载)
    require 本质上是一个函数,只有当代码运行到这一行时,才会去加载模块。因此,你可以把 require 写在 if 语句或者函数里,甚至使用变量作为路径。

    javascript
    // 合法
    let moduleName = 'fs';
    if (condition) {
      const fs = require(moduleName);
    }
  • ES6 Module 是静态的(编译时解析)
    import 语句必须放在文件的最顶层,不能放在代码块(如 if 语句)或函数中(注:ES2020 引入的 import() 函数支持动态加载,但传统的 import 语句是静态的)。模块的依赖关系在代码编译/解析阶段就已经确定了。

    javascript
    // 报错:import 不能放在 if 内部
    if (condition) {
      import { foo } from './foo.js';
    }

    优势:因为是静态的,打包工具(如 Webpack、Vite、Rollup)可以很容易地进行 Tree-Shaking(摇树优化),自动剔除没有被使用到的代码,从而减小最终打包的体积。

③ 加载机制:同步 vs 异步

  • CommonJS 最初为 Node.js 服务器端设计,模块文件一般存放在本地硬盘,读取速度非常快,所以采用同步加载。如果在浏览器端使用,由于网络请求较慢,同步加载会阻塞页面渲染。
  • ES6 Module 充分考虑了浏览器环境,import 命令会被引擎静态分析,可以发起异步网络请求去获取模块文件,不会阻塞后续代码(虽然在 Node.js 中读取本地 ESM 文件很快,但其机制设计是兼容异步特性的)。

④ 顶层 this 的指向

  • CommonJS:在模块顶层打印 this,它指向当前模块的 module.exports 对象。
  • ES6 Module:在模块顶层打印 this,它是 undefined。这是因为 ESM 默认处于严格模式 ("use strict")。

⑤ 循环依赖的处理

  • CommonJS:遇到循环依赖时,只输出已经执行的部分,还未执行的部分不会输出。容易导致拿到的是不完整的对象,引发 undefined 错误。
  • ES6 Module:因为是动态引用,只要开发者保证真正去使用变量的时候,变量已经被初始化,就不会报错。ESM 天生能更好地处理循环依赖。

3. 如何在 Node.js 中使用它们?

Node.js 原生使用 CommonJS。如果你想在 Node.js 中使用 ES6 模块,有以下几种方法:

  1. 把文件后缀名改为 .mjs
  2. 在项目的 package.json 中添加 "type": "module" 字段,这样 .js 文件会被当做 ES6 模块处理(此时如果想用 CommonJS,需把文件后缀改成 .cjs)。

4. 总结

  • 如果你在写纯前端代码(Vue/React/Angular)或使用现代构建工具(Vite/Rollup),你会完全使用 ES6 模块
  • 如果你在写 Node.js 后端代码或脚本,CommonJS 依然非常常见,但现在 Node 社区也在快速向 ES6 模块 迁移。未来的趋势是 ES6 模块大一统。
00:00
00:00