鲁斯前端布鲁斯前端

文章中英模式

常见的前端面试题目 - Code Splitting 与 Dynamic Import 完全解析

深入理解前端优化技术中的代码分割和动态导入,包含实作方式、效能比较,以及面试常见问题的完整解答。

影片縮圖

懒得看文章?那就来看视频吧

基本概念

Code Splitting(代码分割)是一种优化前端应用程序效能的技术,它允许将JavaScript包(bundle)分割成更小的块(chunks),并在需要时才进行载入,而不是一次性载入所有代码。

Dynamic Import(动态导入)是实现代码分割的主要方法,它允许程序在运行时动态载入模块,而不是在应用程序启动时就载入所有模块。

// 传统方式 - 一次性载入
import { heavyFunction } from './heavyModule';

// 动态导入 - 按需载入
button.addEventListener('click', async () => {
  const { heavyFunction } = await import('./heavyModule');
  heavyFunction();
});

为什么需要 Code Splitting?

问题情境

想象一个电商网站,有首页、商品列表、购物车、结账和管理后台等页面。在没有代码分割的情况下:

  • 1. 用户访问首页时会下载整个应用程序的JS代码
  • 2. 包括他们可能永远不会访问的页面(如管理后台)
  • 3. 初次载入时间过长,影响用户体验

效益对比

不使用 Code Splitting使用 Code Splitting
单一大型JS包 (2-5MB+)多个小型JS包 (200-500KB)
初始载入时间长初始载入时间短
页面显示延迟页面快速显示
流量消耗大按需载入,流量优化

实现方式

1. 基于路由的代码分割

// React Router + React.lazy 实现
import { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// 懒加载各个页面组件
const Home = lazy(() => import('./pages/Home'));
const Shop = lazy(() => import('./pages/Shop'));
const Cart = lazy(() => import('./pages/Cart'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/shop" element={<Shop />} />
          <Route path="/cart" element={<Cart />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

2. 基于组件的代码分割

import { lazy, Suspense, useState } from 'react';

// 懒加载大型组件
const HeavyChart = lazy(() => import('./components/HeavyChart'));

function Dashboard() {
  const [showChart, setShowChart] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowChart(true)}>
        显示图表
      </button>
      
      {showChart && (
        <Suspense fallback={<div>图表加载中...</div>}>
          <HeavyChart />
        </Suspense>
      )}
    </div>
  );
}

Dynamic Import 原理

Dynamic Import 是 ECMAScript 的一个特性,使用 import() 函数来动态载入模块:

  • 1. 返回一个 Promise 对象
  • 2. 异步载入指定的模块
  • 3. 仅在需要时才执行载入,不阻塞主线程
  • 4. 支持条件式载入
// 基于条件动态载入
function loadModule(condition) {
  if (condition) {
    import('./moduleA.js')
      .then(module => {
        module.doSomething();
      })
      .catch(error => {
        console.error('模块载入失败:', error);
      });
  } else {
    import('./moduleB.js')
      .then(module => {
        module.doSomethingElse();
      });
  }
}

Dynamic Import 与 Lazy Loading 的关系

  • 1. Dynamic Import 是语言层面的特性,提供按需载入模块的能力
  • 2. Lazy Loading 是一种设计模式,利用 Dynamic Import 来实现延迟载入
  • 3. Lazy Loading 不仅可用于代码分割,还可用于图片、视频等资源

🔥 常见面试题目

(一)Code Splitting 和 Dynamic Import 有什么区别?

解答:Code Splitting 是一种优化技术,目的是将代码分割成多个小块。而 Dynamic Import 是实现 Code Splitting 的一种方法,它使用 import() 语法在运行时动态载入模块。两者关系是:Code Splitting 是目标,Dynamic Import 是工具。

Next.js 15 Bundle 大小比较:

【不使用 Code Splitting】
┌───────────────────────────────────────────────────────┐
│                                                       │
│  主要 Bundle (1.2MB)                                  │
│  ┌─────────────────┬─────────────────┬───────────────┐│
│  │ 页面基本代码     │ 图表库 (600KB)  │ 其他依赖      ││
│  │ (200KB)         │ 包含 D3.js      │ (400KB)       ││
│  └─────────────────┴─────────────────┴───────────────┘│
│                                                       │
└───────────────────────────────────────────────────────┘
  初始载入: 1.2MB (即使用户不查看图表也要下载全部内容)


【使用 Code Splitting】
┌───────────────────────────────┐  ┌───────────────────┐
│                               │  │                   │
│  主要 Bundle (600KB)          │  │  图表 Chunk       │
│  ┌─────────────┬─────────────┐│  │  (600KB)          │
│  │ 页面基本代码 │ 其他依赖    ││  │  - 图表库        │
│  │ (200KB)     │ (400KB)     ││  │  - D3.js          │
│  └─────────────┴─────────────┘│  │  - 相关依赖       │
│                               │  │                   │
└───────────────────────────────┘  └───────────────────┘
  初始载入: 600KB                    按需载入: +600KB
  (减少 50% 初始载入大小)

(二)什么情况下应该使用 Code Splitting?

解答:以下情况特别适合使用代码分割:

应用场景为何需要代码分割
大型单页应用(SPA)SPA一次性载入所有JS会导致初始载入时间过长
包含大量第三方库的应用第三方库通常体积较大,分割后可按需载入
具有复杂后台管理功能的网站普通用户不需要载入管理功能相关代码
包含不常用但体积大的功能图表、编辑器等功能只在特定场景使用,不需要一开始就载入
需要优化首屏载入时间的应用分割后可优先载入关键路径代码,提升首屏速度

(三)实现 Code Splitting 时可能遇到的挑战及解决方案?

解答:

  1. 1. 加载失败处理:使用 try/catch 或 Promise 的 catch 方法处理载入错误
  2. 2. 加载状态:使用 React 的 Suspense 或自定义载入指示器
  3. 3. 重复载入:使用 Webpack 的 SplitChunksPlugin 提取共用代码
  4. 4. 缓存问题:配置合适的缓存策略和文件名哈希
  5. 5. 预加载关键模块:使用 <link rel="preload"> 预先载入重要资源