EsModule和Common.js区别
1. 语法(Import/Export vs Require/Exports)
ESM
使用 静态的 import
和 export
,而 CommonJS
使用 动态的 require
和 module.exports
。
ESModule使用
import
和export
语法来导入和导出模块。导入:
import { functionName } from './module.js';
导出:
export const functionName = () => {};
CommonJS 使用
require
和module.exports
语法来导入和导出模块。导入:
const { functionName } = require('./module');
导出:
module.exports.functionName = () => {};
2. 静态 vs 动态加载
ESM 是静态的,支持 编译时优化,如 Tree Shaking。
CommonJS 是动态的,模块加载是 运行时执行,更灵活,但没有静态优化能力。
ESModule(ESM):
静态分析:
ESM
是静态的,意味着在编译时,浏览器或构建工具就能够分析出所有的依赖关系。这样可以进行优化(如 Tree Shaking)来删除未使用的代码。import
和export
语句必须出现在模块的顶部,不能在条件语句或函数中使用。
CommonJS:
动态加载:
CommonJS
是动态的,模块是在运行时加载的,这意味着require()
调用可以出现在任何地方(例如,在函数或条件语句中)。这为代码的灵活性提供了更多的选择。require()
调用是在执行时解析的,因此不能像import
一样进行静态优化。
3. 加载机制
ESM 支持 异步加载,适用于现代的浏览器和服务器端。
CommonJS 采用 同步加载,适用于 Node.js 环境,通常是基于文件系统的加载方式。
ESModule:模块是 异步加载 的,在浏览器中,
ESM
可以通过网络请求异步加载模块(通过<script type="module">
),而在 Node.js 中,ESM
模块也可以通过import
异步加载。浏览器:
<script type="module" src="module.js">
Node.js:使用
import
语句加载模块。
CommonJS:模块是 同步加载 的。
require()
是在模块执行时同步加载模块的,这意味着当一个模块被加载时,程序会等待该模块加载完成后继续执行。这种方式适合于服务端的应用,如 Node.js。
4. 支持的环境
ESM 是 现代的标准模块化,已被浏览器和 Node.js 逐渐支持。
CommonJS 主要用于 Node.js 环境。
ESModule(ESM):
在现代浏览器中,
ESM
已经得到了广泛支持,可以直接通过<script type="module">
标签在 HTML 页面中使用。在 Node.js 中,从
v12
版本开始也逐渐支持ESM
,但是需要使用.mjs
扩展名或在package.json
文件中设置"type": "module"
。
CommonJS:
主要用于 Node.js 环境中,它在浏览器端原生不被支持,需要通过工具(如 Webpack、Browserify)进行打包。
5. 模块的导出方式
ESM 支持 命名导出 和 默认导出。
CommonJS 使用
module.exports
导出模块,但只能导出一个对象。
ESModule:
export
可以导出多个变量、函数、类等。export const name = 'John'; export function greet() { console.log('Hello'); }
default
导出:每个模块可以有一个默认导出,可以通过export default
来指定。export default function() { console.log('Hello Default'); }
导入:默认导入可以用任意名称来接收导出的默认值。
import func from './module'; // 导入默认导出
CommonJS:
module.exports
用来导出模块。module.exports.name = 'John'; module.exports.greet = function() { console.log('Hello'); };
只能导出一个对象或函数。
导入:
require
导入整个模块。const myModule = require('./module'); myModule.greet();
6. 循环依赖
ESM 和 CommonJS 都能处理循环依赖,但 ESM 由于是静态解析,能更好地避免此类问题,并且避免重复加载模块。
ESModule(ESM):
ESM 支持 静态解析,即使在模块之间存在循环依赖,浏览器和构建工具也能正确处理。在循环依赖的情况下,ESM 会返回 模块的已加载版本,而不会重复加载模块。
CommonJS:
CommonJS 也能处理循环依赖,但它是 在运行时解析 的,可能会返回部分加载的模块内容,而不是完全加载的模块。
7. 支持的功能:动态导入
ESM 支持 动态导入,可以按需加载模块。
CommonJS 只有 同步加载 模块。
ESModule:ESM 支持 动态导入,可以通过
import()
函数动态加载模块。这使得开发者能够按需加载代码,增强性能。import('./module').then(module => {// 使用 module });
CommonJS:CommonJS 没有原生支持动态导入,它的
require
是同步的。
总结对比:
特性 | ESModule (ESM) | CommonJS |
---|---|---|
导入导出语法 | import { x } from 'module' / export | const x = require('module') / module.exports |
模块化方式 | 静态模块化,支持树摇(Tree Shaking) | 动态模块化,按需加载 |
加载方式 | 异步加载,适用于浏览器和 Node.js | 同步加载,主要用于 Node.js |
兼容性 | 支持现代浏览器和 Node.js(通过 .mjs 或 "type": "module" ) | 主要用于 Node.js |
默认导出 | export default | module.exports = |
支持动态导入 | import() 动态导入 | 不支持动态导入 |
循环依赖 | 静态解析,避免重复加载 | 在运行时处理循环依赖,返回部分加载模块 |
使用场景:
ESModules 是现代的标准模块化方案,推荐用于前端开发以及支持
ESM
的 Node.js 环境。CommonJS 主要用于 Node.js 环境,尤其适合传统的服务器端模块。
当浏览器不支持 ES Modules
时
解决方法:使用 script
标签的 type="module"
和回退方案
1. 使用 <script>
标签的 nomodule
属性
HTML5 引入了 nomodule
属性,这个属性可以用来为不支持模块的浏览器提供备用脚本。
原理:
<script type="module">
会在支持ES Modules
的浏览器中加载,而nomodule
会在不支持ES Modules
的浏览器中加载。因此,可以通过这种方式为不支持ES Modules
的浏览器提供传统的、非模块化的 JavaScript。
示例代码:
<!-- 模块化脚本,在支持 ES Module 的浏览器中执行 -->
<script type="module" src="main.mjs"></script><!-- 非模块化脚本,仅在不支持模块的浏览器中执行 -->
<script nomodule src="main-legacy.js"></script>
type="module"
:浏览器支持ES Modules
时,会加载main.mjs
脚本。nomodule
:仅当浏览器不支持模块时,会加载main-legacy.js
脚本。这样,老旧浏览器会执行传统的非模块化脚本。
2. 使用 JavaScript 编译工具(如 Babel)将代码转译为 ES5
如果要支持不支持 ES Modules
的旧版浏览器(如 IE 11 或一些早期版本的 Edge),还需要使用工具将 JavaScript 代码转译为兼容的版本。
Babel:Babel 是一个非常流行的 JavaScript 编译器,它可以将现代 JavaScript 代码(包括
ES Modules
)转译为兼容更广泛浏览器的代码。Webpack 或 Rollup:这些打包工具也可以与 Babel 配合使用,将
ES Modules
转换为传统的 CommonJS 或 IIFE 模式,从而确保兼容旧浏览器。
配置 Babel 转译 ES Modules
:
安装 Babel 和相关插件:
如果你还没有 Babel 环境,可以通过以下命令安装 Babel 和必要的插件:npm install --save-dev @babel/core @babel/preset-env babel-loader
Babel 配置文件(
babel.config.json
):在
babel.config.json
中,配置 Babel 转译ES Modules
为 CommonJS 或其他兼容格式:{"presets": [["@babel/preset-env",{"modules": "commonjs"}]] }
"modules": "commonjs"
:这将ES Modules
转换为CommonJS
,以确保可以在不支持ES Modules
的浏览器中运行。
Webpack 配置(可选):
如果使用 Webpack,你可以配置 Webpack 来确保旧浏览器能够处理编译后的 JavaScript:
// webpack.config.js module.exports = {entry: './src/index.js',output: {filename: 'bundle.js',path: __dirname + '/dist'},module: {rules: [{test: /\.js$/,exclude: /node_modules/,use: {loader: 'babel-loader'}}]} };
最终生成的代码:
Babel 会将现代 JavaScript(如
import
/export
)转译为兼容 ES5 的代码,这样即使在旧浏览器中,也能正常加载并执行。
3. 使用 Polyfill
如果你的 JavaScript 中使用了其他新的 JavaScript 特性(例如 Promise
、fetch
等),而这些特性在旧浏览器中没有实现,可以使用 polyfill 来提供对这些特性的支持。
Polyfill:Polyfill 是一个 JavaScript 库,用于提供对新特性的支持。例如,
core-js
是一个流行的 polyfill 库,可以为不支持的浏览器提供ES6
、ES7
等特性的实现。
如何使用 polyfill:
安装 core-js:
npm install core-js
在 JavaScript 中引入 polyfill:
import 'core-js/stable'; import 'regenerator-runtime/runtime'; // 如果使用了 async/await
这会为不支持的浏览器提供必要的功能实现。
总结:
如果你需要在不支持 ES Modules
的浏览器中运行代码,可以使用以下几种方法:
nomodule
和type="module"
:为支持ES Modules
的浏览器加载模块,为不支持的浏览器加载传统脚本。使用 Babel:通过 Babel 将
ES Modules
转换为兼容旧浏览器的代码。Polyfill:使用 polyfill 库来填补旧浏览器缺少的功能,确保现代 JavaScript 特性在旧浏览器中能够正常运行。