🚀 JavaScript中代码分割和按需加载详解

📅 发布于 2026年1月 | 👤 作者:博主 | 🏷️ 标签:代码分割, 按需加载, 动态导入, React.lazy, 性能优化, Web开发, 前端, 面试

欢迎来到我的博客文章!所有文章都是满满的前端干货,文章简明扼要。

一、核心概念区分

🔹 代码分割(Code Splitting)

含义:构建时将代码拆分成多个独立 bundle(chunks)

目标:✅ 减小主包体积,避免"all-in-one.js"

🔹 按需加载(Lazy Loading)

含义:运行时根据条件动态导入某段代码(如路由、组件、工具库)

目标:✅ 延迟加载非首屏资源,节省带宽与内存

✅ 配合使用:它们常配合使用:先分割 → 再按需加载

🛠️ 二、JavaScript 中的实现方式

动态 import() 语法(ECMAScript 标准)

// 静态导入(构建时打包进主 bundle)
// import { heavyFunc } from './utils/heavy';

// 动态导入(运行时按需加载)
button.addEventListener('click', async () => {
  const { heavyFunc } = await import('./utils/heavy.js');
  heavyFunc();
});

// ✅ 推荐:路径部分固定,便于工具分析
const module = await import(`./pages/${pageName}.js`);

// ❌ 避免:完全动态路径(会导致整个目录被打包!)
// const module = await import(pagePath); // webpack 会把 ./pages/ 下所有文件都打包!

框架级集成(React / Vue)

✅ React:React.lazy + Suspense

// 1. 懒加载组件
const UserProfile = React.lazy(() => import('./UserProfile'));

// 2. 在路由或条件渲染中使用
function App() {
  const [showProfile, setShowProfile] = useState(false);

  return (
    <div>
      <button onClick={() => setShowProfile(true)}>查看资料</button>
      {showProfile && (
        // 2. 用 Suspense 包裹,提供 loading 状态
        <Suspense fallback={<div>加载中...</div>}>
          <UserProfile />
        </Suspense>
      )}
    </div>
  );
}

Vue 3(Composition API)

<!-- 路由级懒加载(推荐) -->
<!-- router/index.ts -->
const routes = [{
  path: '/profile',
  component: () => import('@/views/UserProfile.vue'), // 自动 code split
}];

<!-- 组件级懒加载 -->
<script setup>
import { defineAsyncComponent } from 'vue';

const UserProfile = defineAsyncComponent(() => import('@/components/UserProfile.vue'));
</script>

<template>
  <UserProfile v-if="show" />
</template>

构建工具支持(Vite / Webpack)

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        // 自定义动态 chunk 名
        chunkFileNames: 'chunks/[name]-[hash].js',
      }
    }
  }
});

🧪 三、实战示例:路由级懒加载(React + React Router)

// routes.tsx
import { createBrowserRouter } from 'react-router-dom';

const router = createBrowserRouter([{
  path: '/',
  element: <Layout />,
  children: [{
    index: true,
    element: <Home />,
  }, {
    // ✅ 关键:动态导入 + Suspense 边界
    path: 'dashboard',
    element: (
      <Suspense fallback={<Spinner />}>
        <Dashboard />
      </Suspense>
    ),
    loader: () => import('./pages/Dashboard.loader'), // 数据预加载
  }],
}]);

// Dashboard.loader.ts(搭配 SWR 实现数据+组件并行加载)
export async function loader() {
  // 启动数据请求(不 await)
  const userPromise = fetch('/api/user');
  const statsPromise = fetch('/api/stats');

  // 返回 promises,供组件消费
  return { user: userPromise, stats: statsPromise };
}
✅ 效果:进入 /dashboard 前,先加载 Dashboard 组件代码 + 并行请求数据,用户体验丝滑。

五、工程化建议(前端组长视角)

场景 推荐策略 工具支持
路由 ✅ 全部动态导入 React Router / Vue Router 原生支持
重型组件
(图表、编辑器、PDF 预览)
✅ React.lazy / defineAsyncComponent Suspense + fallback 优化体验
工具函数
(仅少数页面用)
✅ 封装 getXXX() 单例懒加载 避免"import 但未使用"仍打包
模块联邦 Remote ✅ Remote 内部也做 code split Webpack exposes + import()
预加载 🔜 对高概率下一步操作用 prefetch 如:首页按钮 hover 时预加载目标页

🚫 常见陷阱 & 避坑指南

问题 原因 解决方案
动态导入后包反而变大 路径太动态 → Webpack 打包整个目录 ✅ 用模板字符串固定前缀:
import(`./pages/${name}.js`)
Suspense 白屏 fallback 太简单 or 网络极慢 ✅ 加骨架屏 + isValidating 状态
HMR 失效 动态模块未被 Vite 监听 ✅ 确保路径在 src/ 下,避免 ../ 跨目录
模块联邦共享冲突 Remote 的 lazy chunk 与 Host 重复加载依赖 ✅ 共享依赖设为 eager: true + singleton: true
🎯 最佳实践总结:

← 返回首页