全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-16 8 分钟 ✍️ juanwangdev

代码分割与懒加载

代码分割与懒加载是优化首屏加载速度的核心技术,通过按需加载减少初始资源体积。

核心概念

代码分割

将代码拆分为多个chunk,按需加载而非一次性加载全部代码。

懒加载

延迟加载非关键资源,在需要时才加载对应模块。

收益分析

  • 减少首屏加载时间:只加载必要代码
  • 降低初始带宽消耗:按需请求
  • 提高缓存命中率:小块更容易命中缓存

代码分割策略

入口分割

JavaScript
// webpack.config.js
module.exports = {
  entry: {
    main: './src/index.js',
    admin: './src/admin.js',
    vendor: ['lodash', 'axios']
  },
  output: {
    filename: '[name].[contenthash].js'
  }
};

// 生成多个独立入口chunk
// main.js、admin.js、vendor.js

动态导入分割

JavaScript
// 使用import()动态导入
async function loadModule() {
  const module = await import('./heavyModule.js');
  module.doSomething();
}

// 条件加载
if (condition) {
  import('./feature.js').then(module => {
    module.init();
  });
}

// 按路由分割
const routes = {
  home: () => import('./pages/Home.js'),
  admin: () => import('./pages/Admin.js'),
  dashboard: () => import('./pages/Dashboard.js')
};

SplitChunksPlugin

JavaScript
// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all', // 对所有chunk分割

      cacheGroups: {
        // 第三方库单独打包
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          priority: 20
        },

        // 公共模块提取
        commons: {
          minChunks: 2, // 被引用2次以上提取
          name: 'commons',
          chunks: 'all',
          priority: 10
        },

        // UI组件库
        ui: {
          test: /[\\/]src[\\/]components[\\/]/,
          name: 'ui',
          chunks: 'all',
          priority: 15
        }
      }
    }
  }
};

懒加载实现

组件懒加载(React)

JavaScript
import React, { lazy, Suspense } from 'react';

// lazy声明懒加载组件
const Dashboard = lazy(() => import('./Dashboard'));
const AdminPanel = lazy(() => import('./AdminPanel'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Dashboard />
    </Suspense>
  );
}

// 条件渲染懒加载
function ConditionalComponent({ isAdmin }) {
  if (!isAdmin) return null;

  const Admin = lazy(() => import('./Admin'));
  return (
    <Suspense fallback={<div>Loading admin...</div>}>
      <Admin />
    </Suspense>
  );
}

路由懒加载

JavaScript
// React Router
import { lazy, Suspense } from 'react';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));

const routes = [
  { path: '/', element: <Suspense fallback={<Loading />}><Home /></Suspense> },
  { path: '/about', element: <Suspense fallback={<Loading />}><About /></Suspense> }
];

// Vue Router
const routes = [
  {
    path: '/admin',
    component: () => import('./views/Admin.vue')
  },
  {
    path: '/dashboard',
    component: () => import('./views/Dashboard.vue')
  }
];

图片懒加载

JavaScript
// 原生图片懒加载
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy" />

// IntersectionObserver实现
const images = document.querySelectorAll('img[data-src]');

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      observer.unobserve(img);
    }
  });
}, { rootMargin: '100px' });

images.forEach(img => observer.observe(img));

// 组件封装
function LazyImage({ src, placeholder, alt }) {
  const [loaded, setLoaded] = useState(false);
  const imgRef = useRef();

  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) {
        imgRef.current.src = src;
        observer.disconnect();
      }
    });

    observer.observe(imgRef.current);
    return () => observer.disconnect();
  }, [src]);

  return <img ref={imgRef} src={loaded ? src : placeholder} alt={alt} onLoad={() => setLoaded(true)} />;
}

预加载策略

JavaScript
// prefetch:空闲时预加载
import(/* webpackPrefetch: true */ './nextPage.js');

// preload:并行预加载(当前路由必需)
import(/* webpackPreload: true */ './criticalModule.js');

// 手动预加载
function prefetchPage(path) {
  const link = document.createElement('link');
  link.rel = 'prefetch';
  link.href = `/static/js/${path}.js`;
  document.head.appendChild(link);
}

// 鼠标悬停预加载
<Link
  onMouseEnter={() => prefetchPage('/admin')}
  href="/admin"
>
  Admin Panel
</Link>

Vite代码分割

Vite配置

JavaScript
// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['lodash', 'axios'],
          ui: ['react', 'react-dom']
        }
      }
    }
  }
});

// 动态manualChunks函数
manualChunks(id) {
  if (id.includes('node_modules')) {
    if (id.includes('lodash')) return 'lodash';
    if (id.includes('react')) return 'react';
    return 'vendor';
  }
}

Vite懒加载

JavaScript
// 与Webpack语法一致
const module = await import('./module.js');

// 组件懒加载
const LazyComponent = defineAsyncComponent(() =>
  import('./components/Lazy.vue')
);

按需加载优化

骨架屏

JavaScript
// 加载占位
function LoadingSkeleton() {
  return (
    <div className="skeleton">
      <div className="skeleton-header" />
      <div className="skeleton-content" />
    </div>
  );
}

// 使用
<Suspense fallback={<LoadingSkeleton />}>
  <LazyComponent />
</Suspense>

加载状态管理

JavaScript
function LazyLoader({ importFn, children }) {
  const [Component, setComponent] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    importFn()
      .then(module => setComponent(() => module.default))
      .catch(err => setError(err))
      .finally(() => setLoading(false));
  }, [importFn]);

  if (error) return <ErrorComponent error={error} />;
  if (loading) return <LoadingSkeleton />;
  return children(Component);
}

chunk分析

JavaScript
// Webpack Bundle Analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer');

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin.BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false
    })
  ]
};

// Vite分析
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  plugins: [visualizer({ open: false })]
});

最佳实践

分割策略

JavaScript
// ✅ 好的分割策略
optimization: {
  splitChunks: {
    chunks: 'all',
    minSize: 20000, // 最小20KB才分割
    maxSize: 244000, // 最大244KB

    cacheGroups: {
      // 核心库单独打包
      react: { test: /react/, name: 'react' },
      // 工具库单独打包
      lodash: { test: /lodash/, name: 'lodash' }
    }
  }
}

// ❌ 过度分割
// 每个小模块单独打包 → 请求过多

加载优先级

text
核心资源 → 同步加载
首屏必需 → preload
次要页面 → prefetch
非必需 → 懒加载(用户触发)

预加载要适度,prefetch过多会抢占带宽影响首屏。

要点总结

  1. 入口分割:多入口应用天然分割为多个chunk
  2. 动态导入:import()实现按需加载
  3. SplitChunks:提取公共模块、第三方库
  4. React.lazy:配合Suspense实现组件懒加载
  5. prefetch/preload:智能预加载提升体验
  6. 分析工具:Bundle Analyzer定位分割问题

存放路径:articles/JS/专家/高级性能分析/代码分割与懒加载.md

📝 发现内容有误?点击此处直接编辑

← 上一篇 Web Worker 多线程优化
下一篇 → 内存泄漏检测与优化
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库