preload / prefetch / async / defer 核心区别与Next.js实战指南(面试专用)

适配场景:前端Leader岗面试、Web3金融交易场景性能优化、Next.js项目深度调优,覆盖99%的面试考点,从底层原理到实战落地全链路拆解。


一、核心本质分类(面试第一题必答,拉开差距)

很多面试者会把四个概念混为一谈,面试官考察的第一核心,就是你能否清晰区分两类完全不同的技术能力:

分类包含属性核心作用解决的核心问题
脚本加载执行控制async / defer仅作用于<script>标签,控制JS脚本的下载时机、执行时机、是否阻塞页面渲染解决原生JS脚本默认阻塞HTML解析和页面渲染的性能问题
资源预加载指令preload / prefetch作用于<link>标签,控制浏览器对各类资源的预加载行为、优先级、缓存策略,仅负责加载不负责执行解决关键资源加载时机晚、页面渲染/交互卡顿、跳转页面白屏的体验问题

二、逐点深度拆解(底层原理+执行时机+适用场景)

(一)脚本加载控制:async vs defer

前置基础:原生脚本的默认行为

浏览器解析HTML的默认流程:

HTML解析 → 遇到普通<script>标签 → 暂停HTML解析 → 同步下载脚本 → 立即执行脚本 → 恢复HTML解析 → 解析完成触发DOMContentLoaded → 所有资源加载完成触发load

核心问题:普通脚本会完全阻塞HTML解析和渲染,脚本下载+执行的时间内,页面完全白屏,是首屏性能的核心瓶颈。

asyncdefer的核心价值,就是让脚本的下载过程完全不阻塞HTML解析,仅对执行时机做不同控制。


1. defer(延迟执行)

底层原理
  • 下载阶段:遇到带defer的脚本,浏览器异步后台下载脚本,完全不暂停HTML解析
  • 执行阶段:脚本下载完成后,不会立即执行,会等待HTML完全解析完成、DOM树构建完毕,在DOMContentLoaded事件触发之前严格按照脚本在HTML中的出现顺序依次执行;
  • 多个defer脚本:永远按标签出现顺序执行,不会因为下载快慢打乱顺序,依赖关系可正常生效。
执行时间线
HTML解析开始 → 遇到defer脚本 → 异步下载(不暂停解析)→ 继续解析HTML直到完全完成 → 按顺序执行所有defer脚本 → 触发DOMContentLoaded → 触发load事件
核心特性
  • 永远不阻塞HTML解析,仅在HTML解析完成后执行;
  • 严格保证执行顺序,适合有依赖关系的脚本;
  • 执行时DOM已完全构建完成,可正常获取DOM节点;
  • 兼容性:IE9+全支持,现代浏览器100%兼容。
适用场景
  • 有严格依赖关系的核心业务脚本(如框架入口、全局状态、工具库);
  • 需要操作DOM、且不阻塞首屏渲染的脚本;
  • 必须在页面水合/交互前执行,但不阻塞首屏渲染的脚本。

2. async(异步执行)

底层原理
  • 下载阶段:和defer一致,遇到带async的脚本,浏览器异步后台下载,完全不暂停HTML解析
  • 执行阶段:脚本下载完成后,立即暂停HTML解析,强制执行脚本,执行完成后再恢复HTML解析;
  • 多个async脚本:执行顺序完全不确定,哪个脚本先下载完成,就先执行哪个,和标签出现顺序无关。
执行时间线
HTML解析开始 → 遇到async脚本 → 异步下载(不暂停解析)→ 脚本下载完成 → 暂停HTML解析 → 执行脚本 → 恢复HTML解析 → 解析完成 → 触发DOMContentLoaded → 触发load事件
核心特性
  • 下载不阻塞HTML解析,但执行会阻塞HTML解析;
  • 执行顺序不可控,完全由下载速度决定,不适合有依赖关系的脚本;
  • 执行时机不确定,可能在HTML解析完成前/后执行,无法保证DOM已完全构建;
  • 兼容性:IE10+全支持,现代浏览器100%兼容。
适用场景
  • 无任何依赖的独立第三方脚本(如统计、埋点、广告、客服工具);
  • 不依赖DOM、不依赖其他脚本的独立功能脚本;
  • 对执行时机无严格要求,不影响核心业务的非关键脚本。

async vs defer 核心区别对照表(面试必背)

对比维度deferasync
下载是否阻塞HTML解析不阻塞不阻塞
执行是否阻塞HTML解析不阻塞(HTML解析完成后才执行)阻塞(下载完成后立即暂停解析执行)
执行顺序严格按标签在HTML中的出现顺序执行无顺序,先下载完成先执行
执行时机HTML完全解析完成后,DOMContentLoaded之前脚本下载完成后立即执行,与HTML解析并行
DOM可用性执行时DOM已完全构建,可正常操作DOM执行时DOM可能未完成构建,无法保证DOM可用性
重复执行多次添加相同脚本,仅执行一次多次添加相同脚本,会多次下载多次执行
核心适用场景有依赖关系的核心业务脚本、需要操作DOM的脚本无依赖的第三方独立脚本、非关键功能脚本

(二)资源预加载指令:preload vs prefetch

两者都是通过<link rel="xxx">实现的浏览器预加载能力,核心区别是优先级、加载时机、使用场景完全不同


1. preload(预加载当前页关键资源)

底层原理

preload是浏览器的高优先级强制预加载指令,告诉浏览器:这个资源是当前页面必须的关键资源,必须立即以最高优先级下载,缓存到内存中,等到需要使用时,直接从缓存读取,无需等待下载

  • 核心特点:只加载,不执行。预加载的JS/CSS/字体/图片,不会自动执行/渲染,必须等到页面正式引用时才会生效;
  • 优先级:浏览器会给preload资源最高的网络优先级,不会被其他低优先级资源抢占带宽;
  • 生命周期:预加载的资源如果在30秒内没有被页面使用,浏览器会自动丢弃,发出警告,造成带宽浪费。
基础语法
<!-- 预加载JS脚本 -->
<link rel="preload" href="/core-trade.js" as="script" />
<!-- 预加载字体文件(必须加crossorigin) -->
<link rel="preload" href="/font/trade-font.woff2" as="font" type="font/woff2" crossorigin />
<!-- 预加载图片 -->
<link rel="preload" href="/kline-bg.png" as="image" />
<!-- 预加载CSS -->
<link rel="preload" href="/core-trade.css" as="style" />

关键注意:as属性必须填写,告诉浏览器资源类型,否则浏览器会给与很低的优先级,预加载失效。

核心特性
  • 高优先级强制加载,不阻塞HTML解析和渲染;
  • 提前加载关键资源,消除资源加载的延迟,优化LCP、FID等核心Web指标;
  • 支持跨域资源,需添加crossorigin属性;
  • 兼容性:IE不支持,现代浏览器(Chrome/Firefox/Safari 11.1+)全支持。
适用场景
  • 当前页面首屏必须的关键资源(如核心业务JS、首屏字体、LCP大图、核心CSS);
  • 加载时机晚的隐藏资源(如弹窗内的核心脚本、Tab切换后的内容);
  • 金融交易场景的K线图、深度图核心渲染脚本,提前预加载保证交互秒开;
  • 字体文件预加载,避免页面文字闪烁(FOIT/FOUT),优化CLS指标。

2. prefetch(预加载未来页面的资源)

底层原理

prefetch是浏览器的低优先级闲时预加载指令,告诉浏览器:这个资源是用户未来可能访问的下一个页面需要的,在浏览器当前页面空闲、带宽充足时,后台低优先级下载,缓存到磁盘中,用户跳转到对应页面时,直接从缓存读取

  • 核心特点:闲时加载,不影响当前页面。浏览器只会在主线程空闲、网络带宽富余时,才会下载prefetch资源,完全不会抢占当前页面关键资源的带宽;
  • 优先级:网络优先级最低,不会影响当前页面的加载和渲染;
  • 生命周期:预加载的资源会缓存到磁盘中,缓存时间由浏览器的HTTP缓存规则控制,短期(5分钟内)跳转页面可直接复用。
基础语法
<!-- 预加载未来页面的JS脚本 -->
<link rel="prefetch" href="/next-page-trade.js" as="script" />
<!-- 预加载未来页面的图片 -->
<link rel="prefetch" href="/next-page-kline.png" as="image" />
核心特性
  • 低优先级闲时加载,完全不影响当前页面性能;
  • 针对未来页面的资源预加载,大幅提升页面跳转的加载速度,实现秒开;
  • 浏览器不保证一定会加载,若当前页面一直处于繁忙状态,会放弃prefetch;
  • 兼容性:IE不支持,现代浏览器全支持。
适用场景
  • 用户高概率会访问的下一个页面的核心资源(如首页→交易页、列表页→详情页);
  • 新手引导、分步流程的下一步页面资源;
  • 金融交易场景中,行情列表页预加载交易对详情页的核心脚本和数据;
  • 高频访问的二级页面资源,提前预加载提升用户体验。

preload vs prefetch 核心区别对照表(面试必背)

对比维度preloadprefetch
核心目的提前加载当前页面必须的关键资源提前加载未来页面可能用到的资源
加载优先级最高优先级,浏览器强制立即加载最低优先级,浏览器闲时才会加载
阻塞特性不阻塞HTML解析和渲染,不阻塞window.onload完全不影响当前页面的任何加载和渲染
缓存策略加载后缓存到内存,30秒内未使用会被丢弃加载后缓存到磁盘,按HTTP缓存规则长期存储
适用页面仅针对当前页面针对未来的下一个页面
滥用风险高,过度使用会抢占关键资源带宽,导致首屏性能下降低,仅在闲时加载,过度使用仅会浪费用户带宽
核心优化目标优化当前页面的首屏渲染速度、LCP、交互响应速度优化未来页面的跳转加载速度,实现页面秒开

三、Next.js中的专属使用方式与最佳实践

Next.js对上述四个能力做了深度封装和优化,提供了专属API,无需手动写原生标签,同时内置了大量预加载优化,面试中重点考察你对Next.js封装API的理解和最佳实践。

(一)async / defer 在Next.js中的使用:next/script 组件

Next.js官方强烈推荐使用next/script组件替代原生<script>标签,该组件内置了对async/defer的深度封装,通过strategy属性精准控制脚本的加载执行时机,完美适配SSR/SSG/Streaming渲染模式。

1. strategy 属性与 async/defer 的对应关系

strategy 取值对应原生能力核心行为适用场景使用限制
beforeInteractive强化版 defer在HTML流式传输之前加载脚本,在页面水合之前同步执行,阻塞水合但不阻塞HTML渲染核心polyfill、全局配置、必须在水合前执行的脚本仅能在Pages Router的_document.js 或 App Router的根layout.js中使用
afterInteractive(默认值)标准 defer页面水合完成后异步加载脚本,不阻塞HTML解析和渲染,在DOMContentLoaded之后执行第三方统计、埋点、分析工具、非核心业务脚本可在任何页面/组件中使用
lazyOnload闲时版 async浏览器完全空闲时(window.load之后)才加载脚本,完全不影响当前页面的任何性能非关键的第三方脚本(客服、聊天工具、广告、低频功能)可在任何页面/组件中使用
worker专属Web Worker将脚本加载到Web Worker中执行,完全不阻塞主线程heavy计算脚本(行情数据计算、加密解密)Next.js 14+ 支持,需配合App Router

2. 原生 async/defer 的直接使用

next/script组件也支持直接添加async/defer属性,和原生行为完全一致:

import Script from 'next/script';

// 等同于原生带async的script
<Script src="https://third-party-analytics.js" async />

// 等同于原生带defer的script
<Script src="https://core-utils.js" defer />

3. 金融交易场景最佳实践

// App Router 根 layout.tsx
import Script from 'next/script';

export default function RootLayout({ children }) {
  return (
    <html lang="zh-CN">
      <body>
        {children}
        {/* 1. 必须在水合前执行的行情加密工具库,用beforeInteractive */}
        <Script
          src="/libs/trade-encrypt.js"
          strategy="beforeInteractive"
        />
        {/* 2. 统计埋点脚本,水合后加载,不阻塞渲染 */}
        <Script
          src="https://analytics.example.com/script.js"
          strategy="afterInteractive"
        />
        {/* 3. 在线客服脚本,闲时加载,不影响核心交易功能 */}
        <Script
          src="https://kefu.example.com/widget.js"
          strategy="lazyOnload"
        />
      </body>
    </html>
  );
}

(二)preload / prefetch 在Next.js中的使用

Next.js内置了大量自动预加载优化,同时提供了手动控制的API,是面试的高频考点。

这是Next.js最核心、最常用的预加载能力,也是面试必考点。

核心行为
  • 默认行为<Link>组件包裹的路由链接,只要出现在浏览器视口内,Next.js会自动prefetch目标页面的JS代码和静态数据
  • App Router 特性:prefetch仅预加载页面的静态部分,动态服务端组件不会预加载,跳转时才会请求动态数据;
  • Pages Router 特性:prefetch会预加载整个页面的JS代码和getStaticProps的静态数据;
  • 生效范围:仅在生产环境生效,开发环境不会自动prefetch;
  • 手动控制:通过prefetch={false}关闭自动预加载,减少带宽浪费。
实战用法
import Link from 'next/link';

export default function HomePage() {
  return (
    <div>
      {/* 1. 默认开启prefetch:交易页是用户高概率点击的,自动预加载,跳转秒开 */}
      <Link href="/trade/btc-usdt">
        BTC/USDT 交易页
      </Link>

      {/* 2. 关闭prefetch:低频访问的设置页、帮助中心,避免浪费带宽 */}
      <Link href="/settings" prefetch={false}>
        系统设置
      </Link>

      {/* 3. 强制prefetch:隐藏的高频页面,手动强制预加载 */}
      <Link href="/kline-fullscreen" prefetch={true}>
        全屏K线
      </Link>
    </div>
  );
}

2. 资源级preload:Next.js 内置组件优化

Next.js对核心资源的preload做了内置封装,无需手动写<link>标签:

(1)字体预加载:next/font

Next.js官方字体组件默认开启preload,自动预加载页面使用的字体文件,避免文字闪烁,优化CLS指标:

import { Inter } from 'next/font/google';

// 自动preload字体,优化首屏渲染
const inter = Inter({
  subsets: ['latin'],
  preload: true, // 默认开启,可手动关闭
});

export default function RootLayout({ children }) {
  return <html className={inter.className}>{children}</html>;
}
(2)图片预加载:next/image priority 属性

next/image组件添加priority={true},Next.js会自动给图片设置preload,提升LCP指标,是首屏大图优化的官方推荐方案:

import Image from 'next/image';

export default function TradePage() {
  return (
    <div>
      {/* 首屏LCP大图,开启priority,自动preload,优化LCP */}
      <Image
        src="/kline-main-bg.png"
        width={1200}
        height={600}
        priority={true}
        alt="K线图背景"
      />
    </div>
  );
}

3. 手动自定义preload/prefetch

对于特殊场景,Next.js支持手动添加原生preload/prefetch标签,同时提供了React 18+的预加载API:

(1)手动添加preload标签
// App Router 根 layout.tsx
export default function RootLayout({ children }) {
  return (
    <html lang="zh-CN">
      <head>
        {/* 手动preload交易核心脚本 */}
        <link rel="preload" href="/libs/kline-render.js" as="script" />
        {/* 手动prefetch未来页面的资源 */}
        <link rel="prefetch" href="/libs/setting-page.js" as="script" />
      </head>
      <body>{children}</body>
    </html>
  );
}
(2)React 18+ 预加载API

Next.js 13+ 支持React 18的preloadAPI,可在事件处理函数中提前预加载资源,比如鼠标hover到按钮上时,预加载目标页面的资源:

import { preload } from 'react-dom';

export default function TradeCard() {
  const handleMouseEnter = () => {
    // 鼠标hover时,提前预加载交易页的核心资源
    preload('/trade/btc-usdt');
    preload('/libs/kline-render.js', { as: 'script' });
  };

  return (
    <Link 
      href="/trade/btc-usdt"
      onMouseEnter={handleMouseEnter}
    >
      BTC/USDT 交易
    </Link>
  );
}

四、面试高频考点与标准答案

1. 请说一下async和defer的核心区别?

标准答案
async和defer的核心共同点是脚本下载都不会阻塞HTML解析,核心区别有3点:

  1. 执行时机不同:async脚本下载完成后立即执行,会阻塞HTML解析;defer脚本会等待HTML完全解析完成后,在DOMContentLoaded之前执行,不会阻塞HTML解析。
  2. 执行顺序不同:多个defer脚本严格按照标签在HTML中的出现顺序执行,可保证依赖关系;多个async脚本的执行顺序完全不确定,谁先下载完成谁先执行,不适合有依赖关系的脚本。
  3. DOM可用性不同:defer脚本执行时,DOM已完全构建完成,可正常操作DOM;async脚本执行时机不确定,无法保证DOM已完成构建。

2. preload和prefetch的核心区别是什么?

标准答案
两者的核心区别在于加载目的、优先级、适用场景完全不同

  1. 核心目的不同:preload是为了提前加载当前页面必须的关键资源,优化当前页面的首屏性能;prefetch是为了提前加载未来页面可能用到的资源,优化后续页面的跳转速度。
  2. 加载优先级不同:preload是浏览器最高优先级的加载指令,会强制立即加载;prefetch是最低优先级,浏览器只会在闲时、带宽富余时才会加载。
  3. 缓存策略不同:preload的资源缓存到内存,30秒内未使用会被浏览器丢弃;prefetch的资源缓存到磁盘,按HTTP缓存规则长期存储。
  4. 滥用风险不同:preload滥用会抢占关键资源的带宽,导致首屏性能下降,风险极高;prefetch滥用仅会浪费用户带宽,对当前页面性能无影响。

3. Next.js中next/link的prefetch默认行为是什么?

标准答案
next/link的prefetch默认行为分两种路由模式:

  1. App Router:默认prefetch={true},当Link组件出现在浏览器视口内时,Next.js会自动预加载目标路由的静态部分,动态服务端组件不会预加载,仅在用户跳转时请求动态数据;
  2. Pages Router:默认prefetch={true},当Link组件出现在视口内时,Next.js会自动预加载目标页面的完整JS代码和getStaticProps的静态数据;
  3. 补充说明:prefetch仅在生产环境生效,开发环境不会执行;对于动态路由、prefetch={false}的Link,不会自动预加载。

4. 带async/defer的脚本,会阻塞DOMContentLoaded和load事件吗?

标准答案

  1. defer脚本:会在DOMContentLoaded事件触发之前执行,会延迟DOMContentLoaded事件的触发,直到所有defer脚本执行完成;不会阻塞load事件。
  2. async脚本:执行时机不确定,若在HTML解析完成前执行完成,会阻塞DOMContentLoaded;若在HTML解析完成后执行,不会阻塞DOMContentLoaded;无论何时执行,都会阻塞load事件,直到所有async脚本执行完成。

5. 滥用preload会有什么问题?

标准答案
滥用preload会带来3个核心问题:

  1. 带宽抢占:preload会占用最高的网络优先级,过度使用会抢占当前页面关键资源(如首屏JS、CSS、LCP图片)的带宽,反而导致首屏加载变慢,核心指标恶化。
  2. 内存浪费:preload的资源如果30秒内没有被页面使用,会被浏览器自动丢弃,造成用户带宽和内存的浪费,尤其对移动端流量不友好。
  3. 性能负优化:大量preload请求会导致TCP连接拥塞,关键资源的加载延迟增加,同时浏览器的预加载扫描器会失效,反而造成页面渲染卡顿。

五、踩坑避坑指南(面试加分项)

  1. async脚本依赖问题:不要给有依赖关系的脚本加async,比如a.js依赖b.js,两个都加async,若a.js先下载完成,会先执行,此时b.js还未加载,会报undefined错误,这类脚本必须用defer。
  2. preload资源必须使用:preload的资源必须在当前页面被引用,否则浏览器会发出警告,30秒后丢弃资源,造成带宽浪费。
  3. preload字体必须加crossorigin:预加载字体文件时,必须添加crossorigin属性,哪怕是同域字体,否则浏览器会重复下载,预加载失效。
  4. defer脚本不要监听DOMContentLoaded:defer脚本会在DOMContentLoaded之前执行,脚本内监听DOMContentLoaded事件,永远不会触发。
  5. Next.js中不要过度prefetch:不要给所有Link都开启prefetch,低频访问的页面必须关闭prefetch,避免浪费用户带宽,尤其对移动端用户不友好。
  6. 不要用preload替代prefetch:preload是给当前页面关键资源用的,未来页面的资源必须用prefetch,否则会抢占当前页面的带宽,造成性能负优化。

六、金融交易场景专属最佳实践

  1. 交易页核心资源preload:对K线图渲染脚本、交易核心SDK、首屏LCP图片,用preload/priority提前加载,保证交易页打开秒渲染,交互无延迟。
  2. 行情列表页prefetch交易页:行情卡片用next/link包裹,开启prefetch,用户hover/看到行情卡片时,自动预加载对应交易对的交易页资源,点击跳转秒开。
  3. 脚本分级加载:核心交易加密、行情计算脚本用beforeInteractive/defer,保证执行顺序和依赖关系;统计、客服等非关键脚本用lazyOnload,不阻塞核心交易功能。
  4. 动态内容预加载:用户鼠标hover到行情卡片时,通过React preload API提前预加载K线历史数据,用户进入交易页时,数据已缓存,K线图秒渲染。
  5. 低频页面关闭prefetch:设置页、帮助中心、关于我们等低频访问页面,给next/link设置prefetch={false},减少带宽占用,避免影响核心交易页面的性能。