返回首页

CommonJS规范 ESM规范

布莱克2026-05-11 16:36
Tip:文章封面与内容无关,作者旅游时拍摄,因为没什么值得把四季都错过!

require: CommonJS规范,最初用于Node.js环境

import: ES6模块规范,JavaScript官方标准

维度requireimport
规范CommonJSES Module
加载时机运行时(代码执行到才加载)编译时(代码执行前就解析)
位置限制任何位置必须在模块顶部
动态路径✅ 支持❌ 不支持(需 import())
值的性质值的拷贝实时只读引用
Tree Shaking❌ 不支持✅ 支持
// require - CommonJS 规范
const module = require('./module')
const { fn1, fn2 } = require('./module')

// import - ES Module 规范
import module from './module'
import { fn1, fn2 } from './module'


加载时机:

require是运行时加载,代码执行到那一行才加载

import是编译时加载,代码执行前就已经加载解析

// require - 运行时加载
console.log('start')
const math = require('./math')  // 执行到这里才会加载
console.log(math.add(1, 2))

// import - 静态加载(编译时)
import { add } from './math'  // 代码执行前就已完成加载和解析
console.log('start')
console.log(add(1, 2))


require的优势和不足:

✅ 优势:

// 1. 真正的动态加载,节省资源
if (feature.isEnabled) {
  const feature = require('./feature')  // 不启用就不加载
}

// 2. 适合大型应用的分层加载
const middleware = []
if (env === 'dev') {
  middleware.push(require('./dev-tools'))
}

// 3. 配置文件的天然支持
const config = require(`./config/${NODE_ENV}.json`)

// 4. 运行时修改模块(热替换)
delete require.cache[moduleId]
const newModule = require(moduleId)  // 重新加载

❌ 不足:

// 1. 无法 Tree Shaking
// 即使只用到 one 函数,整个 utils 都会打包
const utils = require('./utils')
utils.one()

// 2. 同步阻塞
const hugeLib = require('./huge-lib')  // 卡住直到加载完成

// 3. 无法静态分析
// 打包工具不知道哪些模块被使用了
// 导致代码分割困难

// 4. 循环依赖可能产生不完整对象
// a.js
const b = require('./b')
module.exports = { value: 1, b }
// 此时 b 可能还是空对象


import的优势和不足:

✅ 优势:

// 1. Tree Shaking - 只打包使用的代码
import { debounce } from 'lodash-es'  
// 只会打包 debounce,其他函数被 tree-shake 掉

// 2. 异步加载(无需额外配置)
const module = await import('./heavy-module')

// 3. 更容易做代码分割
// Webpack/Vite 能根据 import 自动分割 chunk
const AdminPage = lazy(() => import('./admin/AdminPage'))

// 4. 更好的静态分析
// IDE 能准确知道依赖关系,提供智能提示
// 可以做类型推导、自动导入等高级特性

// 5. 更容易实现变量绑定(实时引用)
// 导出的是引用,不是值拷贝

❌ 不足:

// 1. 动态能力弱(需要特殊语法)
// 不能直接写变量路径
import('./locale/' + lang + '.json')  // 能工作但有限制
// 打包工具会生成多个 chunk,可能不是你想要的

// 2. 严格的循环依赖检测(可能误杀合法场景)
// a.mjs
import { b } from './b.mjs'
export const a = () => b()

// 3. 加载时机固定
// 即使后面没用到,也会加载执行
import hugeLib from './huge-lib'  // 一定会加载

// 4. 需要在模块顶层使用
if (true) {
  import { x } from './x'  // ❌ 语法错误
}
// 只能用 import() 动态导入,但那是异步的


为什么CommonJS不能实现摇树优化:

Tree Shaking 的本质是编译时静态分析,删除未使用的代码。但 CommonJS 的模块结构是动态的

require 是运行时加载,代码执行到 require 语句时才去读取、解析、执行模块。这给了它很大的灵活性——可以在任何位置调用,可以使用动态路径,可以根据条件加载。但打包工具无法做静态分析,不支持 Tree Shaking,因为模块的导出结构可能在运行时发生变化。


assistant