📜 浏览器厨房的故事:当 defer 和 async 成为你的外卖小哥

448 阅读3分钟

📜 浏览器厨房的故事:当 defer 和 async 成为你的外卖小哥

—— 如何用异步加载让网页像奶茶一样丝滑

image.png

🚦 场景一:饿着肚子等外卖(同步脚本的痛点)

<!-- 老板,我要一份巨无霸汉堡(同步脚本) -->
<script src="heavy-burger.js"></script>
<!-- 等待汉堡送达前,你只能干瞪眼(阻塞渲染) -->
<div id="content">我是无辜的DOM元素,但老板不让我上桌😭</div>

此时浏览器就像个固执的吃货:必须等汉堡吃完(脚本加载执行完毕),才允许其他菜品(DOM渲染)上桌。用户看到的将是一片空白,仿佛在说:"老板,我的网页呢?"

Lighthouse 警告
⚠️ "Avoid render-blocking resources"
(翻译:你的网页加载速度比树懒还慢!)


🛵 场景二:雇佣外卖小哥(defer vs async)

我们请来两位性格迥异的外卖骑手:

<!-- 骑手A:强迫症患者 defer -->
<script src="cola.js" defer></script>

<!-- 骑手B:闪电侠 async -->
<script src="fries.js" async></script>
两位骑手的秘密任务手册
骑手行动模式适用场景
defer1. 接单后立即出发(异步加载)
2. 严格按照接单顺序送货(保持执行顺序)
3. 等所有食材摆好才上桌(DOM解析完成后执行)
需要保证顺序的套餐(如 jQuery + 插件)
async1. 接单后立即出发(异步加载)
2. 谁先到谁先上桌(加载完立即执行)
3. 可能把薯条倒在沙拉上(执行顺序不可控)
独立小食(如统计代码、广告)

🧪 实验时间:模拟骑手配送

<script src="a.js" defer></script>
<script src="b.js" async></script>
<script src="c.js" defer></script>

浏览器后台日志

[加载开始] a.js、b.js、c.js 同时出发取餐  
[加载完成] b.js 最快到达!立即执行(DOM可能还没准备好)  
[加载完成] a.js 到达,但坚持要等厨房收拾干净(DOM解析完成)  
[加载完成] c.js 到达,默默排在 a.js 后面  
[DOM解析完成] 按顺序执行 a.js → c.js  

🚀 场景三:React/Vue 厨房的秘密配方

现代前端框架的工程化后厨早已自动化处理脚本加载,比如:

Webpack 大厨的魔法配方

// webpack.config.js
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all', // 把食材切成小块方便配送
    },
  },
};

构建后生成的 HTML:

<script src="main.js" defer></script>   <!-- 主菜(框架代码) -->
<script src="chunk-vendors.js" defer></script> <!-- 调料包(依赖库) -->
<script src="src_views_Home.js" async></script> <!-- 动态加载的甜点 -->

秘密武器

  • preload:提前预约重要食材(关键资源预加载)
  • prefetch:闲时准备可能需要的食材(非关键资源预加载)

🛠️ 手把手改造:优化你的第三方脚本

案例:给网页添加「吃了么」统计代码

<!-- 错误示范:让用户饿着肚子等统计 -->
<script src="https://吃了么统计.com/analytics.js"></script>

<!-- 正确姿势:让闪电侠 async 去送 -->
<script async src="https://吃了么统计.com/analytics.js"></script>

<!-- 高级技巧:异步加载 + 超时保险 -->
<script>
  setTimeout(() => {
    const script = document.createElement('script');
    script.src = 'https://吃了么统计.com/analytics.js';
    script.async = true;
    document.body.appendChild(script);
  }, 3000); // 等用户喝完汤(首屏加载完)再送甜点
</script>

🧠 灵魂总结:选择骑手的黄金法则

image.png

  1. 关键路径脚本(如框架初始化)→ defer(保证顺序不阻塞)
  2. 独立第三方脚本(统计、广告)→ async(谁快谁上)
  3. 巨型资源初始化 → 动态加载 + Promise(精细控制)

性能优化真言

"让该快的快(async),让该等的等(defer),让不该存在的消失(删掉冗余脚本)"


🎉 现在,打开你的 Chrome DevTools,到 Performance 面板看看哪些脚本还在「阻塞厨房」吧!浏览器的食客们(用户)正在等待更丝滑的用餐体验~