JavaScript中require与import的区别详解
前言
在JavaScript开发中,模块化是组织代码的重要方式。随着JavaScript生态的发展,出现了不同的模块系统,其中最常见的是CommonJS(使用require)和ES Modules(使用import)。本文将详细介绍这两种模块导入方式的区别、使用场景和最佳实践。
一、模块系统概述
1.1 什么是模块化
模块化是将复杂程序分解为独立、可重用的代码块的编程方法。每个模块都有自己的作用域,可以导出功能供其他模块使用。
模块化的优势
- 代码复用:避免重复编写相同功能
- 命名空间:避免全局变量污染
- 依赖管理:明确模块间的依赖关系
- 按需加载:提高应用性能
- 团队协作:便于多人协作开发
1.2 JavaScript模块系统发展历程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| var myLibrary = { method1: function() {}, method2: function() {} };
(function(global) { var myLibrary = { method1: function() {}, method2: function() {} }; global.myLibrary = myLibrary; })(window);
const myLibrary = require('./myLibrary');
define(['dependency'], function(dep) { return { method1: function() {} }; });
import myLibrary from './myLibrary.js';
|
二、CommonJS与require
2.1 CommonJS规范
CommonJS是Node.js采用的模块规范,主要用于服务器端JavaScript开发。
基本语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
module.exports = { add: add, subtract: subtract };
module.exports.add = add; module.exports.subtract = subtract;
exports.add = add; exports.subtract = subtract;
|
1 2 3 4 5 6 7
|
const math = require('./math'); const { add, subtract } = require('./math');
console.log(math.add(5, 3)); console.log(subtract(5, 3));
|
2.2 require的特点
同步加载
1 2 3 4
| console.log('开始加载模块'); const fs = require('fs'); console.log('模块加载完成');
|
动态加载
1 2 3 4 5 6 7 8 9 10 11 12 13
| function loadModule(moduleName) { if (moduleName === 'math') { return require('./math'); } else if (moduleName === 'utils') { return require('./utils'); } }
if (process.env.NODE_ENV === 'development') { const devTools = require('./dev-tools'); }
|
缓存机制
1 2 3 4 5 6 7 8 9 10 11
| const math1 = require('./math'); const math2 = require('./math');
console.log(math1 === math2);
console.log(require.cache);
delete require.cache[require.resolve('./math')];
|
2.3 CommonJS的导出方式
module.exports vs exports
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
module.exports = { add: (a, b) => a + b, subtract: (a, b) => a - b };
module.exports.multiply = (a, b) => a * b;
exports.divide = (a, b) => a / b;
|
不同的导出模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| module.exports = { name: 'MyModule', version: '1.0.0', init: function() {} };
module.exports = function(options) { return { start: function() {}, stop: function() {} }; };
module.exports = class Calculator { add(a, b) { return a + b; } subtract(a, b) { return a - b; } };
module.exports = 'Hello World'; module.exports = 42; module.exports = true;
|
三、ES Modules与import
3.1 ES Modules规范
ES Modules(ESM)是ECMAScript 2015(ES6)引入的官方模块系统,现在是JavaScript的标准。
基本语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }
function multiply(a, b) { return a * b; }
function divide(a, b) { return a / b; }
export { multiply, divide };
export default class Calculator { constructor() { this.result = 0; } add(num) { this.result += num; return this; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
import { add, subtract } from './math.js';
import Calculator from './math.js';
import Calculator, { add, subtract } from './math.js';
import { add as sum, subtract as minus } from './math.js';
import * as math from './math.js';
import './init.js';
|
3.2 import的特点
静态分析
1 2 3 4 5 6 7 8 9 10 11 12
| import { add } from './math.js';
if (condition) { import { add } from './math.js'; }
if (condition) { const { add } = await import('./math.js'); }
|
异步加载
1 2 3 4 5 6 7
| console.log('开始');
import { add } from './math.js';
console.log('结束');
|
动态导入(ES2020)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| async function loadMath() { try { const mathModule = await import('./math.js'); console.log(mathModule.add(2, 3)); } catch (error) { console.error('模块加载失败:', error); } }
if (needsMathModule) { import('./math.js').then(module => { console.log(module.add(1, 2)); }); }
button.addEventListener('click', async () => { const { heavyFunction } = await import('./heavy-module.js'); heavyFunction(); });
|
3.3 ES Modules的导出方式
命名导出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
export const PI = 3.14159; export let counter = 0;
export function increment() { counter++; }
export class EventEmitter { constructor() { this.events = {}; } }
const config = { debug: true }; const version = '1.0.0';
export { config, version };
function internalFunction() {} export { internalFunction as publicFunction };
|
默认导出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
export default function log(message) { console.log(`[LOG] ${message}`); }
function createLogger(prefix) { return function(message) { console.log(`[${prefix}] ${message}`); }; }
export default createLogger;
export default { log: (msg) => console.log(msg), error: (msg) => console.error(msg), warn: (msg) => console.warn(msg) };
|
重新导出
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
export { add, subtract } from './math.js'; export { log, error } from './logger.js';
export { default as Calculator } from './calculator.js';
export * from './utils.js';
export { default as MathUtils } from './math.js';
|
四、require vs import 详细对比
4.1 语法差异对比表
特性 |
require (CommonJS) |
import (ES Modules) |
语法风格 |
函数调用 |
声明语句 |
加载时机 |
运行时 |
编译时 |
加载方式 |
同步 |
异步 |
动态加载 |
支持 |
ES2020后支持 |
静态分析 |
不支持 |
支持 |
Tree Shaking |
不支持 |
支持 |
循环依赖 |
部分支持 |
更好支持 |
浏览器支持 |
需要打包工具 |
现代浏览器原生支持 |
4.2 使用场景对比
require适用场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const express = require('express'); const fs = require('fs');
if (process.env.NODE_ENV === 'development') { const devMiddleware = require('./dev-middleware'); }
const moduleName = getUserInput(); const module = require(`./modules/${moduleName}`);
const _ = require('lodash'); const moment = require('moment');
|
import适用场景
1 2 3 4 5 6 7 8 9 10 11 12
| import React from 'react'; import { useState, useEffect } from 'react';
import { debounce } from 'lodash-es';
import * as utils from './utils/index.js';
const LazyComponent = React.lazy(() => import('./LazyComponent'));
|
4.3 性能对比
打包体积
1 2 3 4 5 6
| const _ = require('lodash'); const debounce = _.debounce;
import { debounce } from 'lodash-es';
|
加载性能
1 2 3 4 5
| const heavyModule = require('./heavy-module');
const loadHeavyModule = () => import('./heavy-module');
|
五、实际应用示例
5.1 Node.js项目中的模块管理
package.json配置
1 2 3 4 5 6 7 8 9
| { "name": "my-node-app", "version": "1.0.0", "type": "module", "main": "index.js", "scripts": { "start": "node index.js" } }
|
混合使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| module.exports = { port: process.env.PORT || 3000, database: { host: 'localhost', port: 5432 } };
import express from 'express'; import { createRequire } from 'module';
const require = createRequire(import.meta.url); const config = require('./config.js');
const app = express();
app.listen(config.port, () => { console.log(`Server running on port ${config.port}`); });
|
5.2 前端项目中的模块管理
Webpack配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: __dirname + '/dist' }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ] } };
|
模块组织示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| export { default as http } from './http.js'; export { default as storage } from './storage.js'; export { default as validator } from './validator.js';
export { default as Header } from './Header.js'; export { default as Footer } from './Footer.js'; export { default as Sidebar } from './Sidebar.js';
import { http, storage } from './utils/index.js'; import { Header, Footer } from './components/index.js';
async function initApp() { const config = await http.get('/api/config'); storage.set('config', config); document.body.appendChild(Header()); document.body.appendChild(Footer()); }
initApp();
|
六、最佳实践与建议
6.1 选择指南
何时使用require
- Node.js服务器端项目
- 需要动态加载模块
- 老项目维护
- 简单的脚本工具
何时使用import
- 现代前端项目
- 需要Tree Shaking优化
- 静态分析和类型检查
- 新项目开发
6.2 迁移策略
从CommonJS迁移到ES Modules
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| { "type": "module" }
module.exports = { add: (a, b) => a + b };
export const add = (a, b) => a + b; export default { add };
const { add } = require('./utils');
import { add } from './utils.js';
|
6.3 常见问题与解决方案
循环依赖问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { b } from './b.js'; export const a = 'a'; console.log(b);
import { a } from './a.js'; export const b = 'b'; console.log(a);
import * as bModule from './b.js'; export const a = 'a'; setTimeout(() => console.log(bModule.b), 0);
|
文件扩展名问题
1 2 3 4 5 6
| import utils from './utils';
import utils from './utils.js'; import utils from './utils.mjs';
|
七、总结
7.1 核心差异总结
- 语法层面:require是函数调用,import是语言关键字
- 加载时机:require运行时加载,import编译时分析
- 加载方式:require同步,import异步
- 优化支持:import支持Tree Shaking,require不支持
- 浏览器支持:import原生支持,require需要打包工具
7.2 选择建议
- 新项目:优先选择ES Modules(import/export)
- Node.js项目:可以继续使用CommonJS,或逐步迁移到ES Modules
- 前端项目:强烈推荐使用ES Modules
- 库开发:提供两种格式的版本以兼容不同环境
7.3 未来趋势
ES Modules已经成为JavaScript模块化的标准,随着Node.js对ES Modules支持的完善和浏览器的普及,import/export将成为主流。但CommonJS在Node.js生态中仍有重要地位,两者在相当长的时间内会并存。
理解这两种模块系统的差异和适用场景,能够帮助开发者在不同项目中做出正确的技术选择,写出更高质量的JavaScript代码。