Skip to content

11.1 页面加载优化

关键词: 关键渲染路径, 资源压缩, 缓存策略, 延迟加载, 预加载技术, 性能优化, 加载时间, 用户体验

学习目标

  • 深入理解关键渲染路径的工作原理
  • 掌握各种资源压缩技术和工具
  • 学会设计和实施有效的缓存策略
  • 掌握延迟加载和预加载技术的实现
  • 了解页面加载性能优化的最佳实践

11.1.1 关键渲染路径

关键渲染路径概述

关键渲染路径是浏览器从接收HTML、CSS和JavaScript到渲染出像素画面的过程。优化这个过程是提升页面性能的关键。

html
<!-- 优化关键渲染路径的HTML结构 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>页面加载优化示例</title>
    
    <!-- 关键CSS内联 -->
    <style>
        /* 首屏关键样式 */
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
            margin: 0;
            padding: 0;
            line-height: 1.6;
        }
        
        .header {
            background: #2c3e50;
            color: white;
            padding: 1rem;
            text-align: center;
        }
        
        .loading-placeholder {
            height: 200px;
            background: #f8f9fa;
            display: flex;
            align-items: center;
            justify-content: center;
            color: #666;
        }
        
        /* 首屏加载动画 */
        .loader {
            border: 4px solid #f3f3f3;
            border-top: 4px solid #3498db;
            border-radius: 50%;
            width: 40px;
            height: 40px;
            animation: spin 1s linear infinite;
        }
        
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
    
    <!-- 预加载关键资源 -->
    <link rel="preload" href="critical-font.woff2" as="font" type="font/woff2" crossorigin>
    <link rel="preload" href="hero-image.jpg" as="image">
    
    <!-- 预连接到外部域名 -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="dns-prefetch" href="https://api.example.com">
</head>
<body>
    <header class="header">
        <h1>性能优化示例</h1>
    </header>
    
    <main>
        <!-- 首屏内容 -->
        <section class="hero">
            <div class="loading-placeholder">
                <div class="loader"></div>
                <span>加载中...</span>
            </div>
        </section>
        
        <!-- 非关键内容使用懒加载 -->
        <section class="content" data-lazy-load>
            <!-- 内容将通过JavaScript延迟加载 -->
        </section>
    </main>
    
    <!-- 非关键CSS延迟加载 -->
    <link rel="preload" href="non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
    <noscript><link rel="stylesheet" href="non-critical.css"></noscript>
    
    <!-- JavaScript放在页面底部 -->
    <script>
        // 关键JavaScript内联
        (function() {
            // 页面性能监控
            window.addEventListener('load', function() {
                const perfData = performance.timing;
                const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;
                console.log('页面加载时间:', pageLoadTime + 'ms');
            });
            
            // 延迟加载非关键内容
            function loadNonCriticalContent() {
                const lazyElements = document.querySelectorAll('[data-lazy-load]');
                lazyElements.forEach(element => {
                    // 加载内容的逻辑
                    element.innerHTML = '<p>延迟加载的内容已显示</p>';
                });
            }
            
            // 页面加载完成后延迟加载
            window.addEventListener('load', function() {
                setTimeout(loadNonCriticalContent, 100);
            });
        })();
    </script>
</body>
</html>

关键渲染路径优化策略

html
<!-- 关键渲染路径优化示例 -->
<div class="critical-path-optimization">
    <h3>关键渲染路径优化策略</h3>
    
    <div class="strategy-grid">
        <div class="strategy-card">
            <h4>1. 减少关键资源数量</h4>
            <div class="example">
                <p>将关键CSS内联到HTML中:</p>
                <pre><code>&lt;style&gt;
/* 首屏关键样式 */
.above-fold { ... }
&lt;/style&gt;</code></pre>
            </div>
        </div>
        
        <div class="strategy-card">
            <h4>2. 减少关键字节数</h4>
            <div class="example">
                <p>压缩CSS和JavaScript:</p>
                <pre><code>/* 压缩前 */
.header {
    background-color: #ffffff;
    padding: 20px;
}

/* 压缩后 */
.header{background:#fff;padding:20px}</code></pre>
            </div>
        </div>
        
        <div class="strategy-card">
            <h4>3. 优化关键路径长度</h4>
            <div class="example">
                <p>减少CSS中的@import使用:</p>
                <pre><code>&lt;!-- 避免 --&gt;
&lt;style&gt;@import url('styles.css');&lt;/style&gt;

&lt;!-- 推荐 --&gt;
&lt;link rel="stylesheet" href="styles.css"&gt;</code></pre>
            </div>
        </div>
    </div>
</div>

<style>
    .critical-path-optimization {
        max-width: 1200px;
        margin: 0 auto;
        padding: 20px;
    }
    
    .critical-path-optimization h3 {
        text-align: center;
        color: #333;
        margin-bottom: 30px;
    }
    
    .strategy-grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
        gap: 30px;
    }
    
    .strategy-card {
        background: white;
        padding: 25px;
        border-radius: 12px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
        border-left: 4px solid #3498db;
    }
    
    .strategy-card h4 {
        color: #2c3e50;
        margin: 0 0 15px 0;
    }
    
    .example {
        background: #f8f9fa;
        padding: 15px;
        border-radius: 8px;
        margin-top: 10px;
    }
    
    .example p {
        color: #666;
        margin: 0 0 10px 0;
        font-size: 14px;
    }
    
    .example pre {
        margin: 0;
        overflow-x: auto;
    }
    
    .example code {
        background: #2c3e50;
        color: #ecf0f1;
        padding: 10px;
        border-radius: 4px;
        display: block;
        font-size: 12px;
        line-height: 1.4;
    }
</style>

11.1.2 资源压缩

HTML压缩

html
<!-- HTML压缩示例 -->
<div class="html-compression">
    <h3>HTML压缩技术</h3>
    
    <div class="compression-comparison">
        <div class="before-compression">
            <h4>压缩前的HTML</h4>
            <pre><code>&lt;!DOCTYPE html&gt;
&lt;html lang="zh-CN"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;示例页面&lt;/title&gt;
    
    &lt;!-- 这是一个注释 --&gt;
    &lt;style&gt;
      .container {
        max-width: 1200px;
        margin: 0 auto;
        padding: 20px;
      }
      
      .header {
        background-color: #333333;
        color: #ffffff;
        padding: 1rem;
      }
    &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div class="container"&gt;
      &lt;header class="header"&gt;
        &lt;h1&gt;页面标题&lt;/h1&gt;
      &lt;/header&gt;
      
      &lt;main&gt;
        &lt;p&gt;页面内容&lt;/p&gt;
      &lt;/main&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
        </div>
        
        <div class="after-compression">
            <h4>压缩后的HTML</h4>
            <pre><code>&lt;!DOCTYPE html&gt;&lt;html lang="zh-CN"&gt;&lt;head&gt;&lt;meta charset="UTF-8"&gt;&lt;meta name="viewport" content="width=device-width,initial-scale=1.0"&gt;&lt;title&gt;示例页面&lt;/title&gt;&lt;style&gt;.container{max-width:1200px;margin:0 auto;padding:20px}.header{background-color:#333;color:#fff;padding:1rem}&lt;/style&gt;&lt;/head&gt;&lt;body&gt;&lt;div class="container"&gt;&lt;header class="header"&gt;&lt;h1&gt;页面标题&lt;/h1&gt;&lt;/header&gt;&lt;main&gt;&lt;p&gt;页面内容&lt;/p&gt;&lt;/main&gt;&lt;/div&gt;&lt;/body&gt;&lt;/html&gt;</code></pre>
        </div>
    </div>
    
    <div class="compression-benefits">
        <h4>压缩效果</h4>
        <div class="benefits-grid">
            <div class="benefit-item">
                <span class="benefit-label">文件大小减少</span>
                <span class="benefit-value">~30-40%</span>
            </div>
            <div class="benefit-item">
                <span class="benefit-label">传输时间减少</span>
                <span class="benefit-value">~25-35%</span>
            </div>
            <div class="benefit-item">
                <span class="benefit-label">带宽节省</span>
                <span class="benefit-value">显著</span>
            </div>
        </div>
    </div>
</div>

<style>
    .html-compression {
        max-width: 1200px;
        margin: 0 auto;
        padding: 20px;
    }
    
    .html-compression h3 {
        text-align: center;
        color: #333;
        margin-bottom: 30px;
    }
    
    .compression-comparison {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 30px;
        margin-bottom: 30px;
    }
    
    .before-compression,
    .after-compression {
        background: white;
        padding: 20px;
        border-radius: 12px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    }
    
    .before-compression h4 {
        color: #e74c3c;
        margin: 0 0 15px 0;
    }
    
    .after-compression h4 {
        color: #27ae60;
        margin: 0 0 15px 0;
    }
    
    .compression-comparison pre {
        margin: 0;
        overflow-x: auto;
    }
    
    .compression-comparison code {
        background: #2c3e50;
        color: #ecf0f1;
        padding: 15px;
        border-radius: 8px;
        display: block;
        font-size: 11px;
        line-height: 1.4;
        white-space: pre-wrap;
        word-break: break-all;
    }
    
    .compression-benefits {
        background: #f8f9fa;
        padding: 20px;
        border-radius: 12px;
    }
    
    .compression-benefits h4 {
        color: #333;
        margin: 0 0 15px 0;
        text-align: center;
    }
    
    .benefits-grid {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 20px;
    }
    
    .benefit-item {
        text-align: center;
        padding: 15px;
        background: white;
        border-radius: 8px;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
    
    .benefit-label {
        display: block;
        color: #666;
        font-size: 14px;
        margin-bottom: 5px;
    }
    
    .benefit-value {
        display: block;
        color: #27ae60;
        font-weight: bold;
        font-size: 18px;
    }
    
    @media (max-width: 768px) {
        .compression-comparison {
            grid-template-columns: 1fr;
        }
        
        .benefits-grid {
            grid-template-columns: 1fr;
        }
    }
</style>

CSS和JavaScript压缩

html
<!-- CSS和JavaScript压缩示例 -->
<div class="css-js-compression">
    <h3>CSS和JavaScript压缩</h3>
    
    <div class="compression-techniques">
        <div class="technique-card">
            <h4>CSS压缩技术</h4>
            <div class="technique-example">
                <div class="code-before">
                    <h5>压缩前:</h5>
                    <pre><code>/* 按钮样式 */
.button {
    background-color: #3498db;
    border: none;
    border-radius: 4px;
    color: #ffffff;
    cursor: pointer;
    display: inline-block;
    font-family: Arial, sans-serif;
    font-size: 16px;
    font-weight: 600;
    line-height: 1.5;
    margin: 0;
    padding: 12px 24px;
    text-align: center;
    text-decoration: none;
    transition: background-color 0.3s ease;
    user-select: none;
    vertical-align: middle;
    white-space: nowrap;
}

.button:hover {
    background-color: #2980b9;
}

.button:active {
    background-color: #1e6a96;
}</code></pre>
                </div>
                
                <div class="code-after">
                    <h5>压缩后:</h5>
                    <pre><code>.button{background-color:#3498db;border:none;border-radius:4px;color:#fff;cursor:pointer;display:inline-block;font-family:Arial,sans-serif;font-size:16px;font-weight:600;line-height:1.5;margin:0;padding:12px 24px;text-align:center;text-decoration:none;transition:background-color .3s ease;user-select:none;vertical-align:middle;white-space:nowrap}.button:hover{background-color:#2980b9}.button:active{background-color:#1e6a96}</code></pre>
                </div>
            </div>
        </div>
        
        <div class="technique-card">
            <h4>JavaScript压缩技术</h4>
            <div class="technique-example">
                <div class="code-before">
                    <h5>压缩前:</h5>
                    <pre><code>// 图片懒加载功能
function initLazyLoading() {
    const images = document.querySelectorAll('img[data-src]');
    const imageObserver = new IntersectionObserver(function(entries) {
        entries.forEach(function(entry) {
            if (entry.isIntersecting) {
                const image = entry.target;
                image.src = image.getAttribute('data-src');
                image.removeAttribute('data-src');
                imageObserver.unobserve(image);
            }
        });
    });
    
    images.forEach(function(image) {
        imageObserver.observe(image);
    });
}

// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
    initLazyLoading();
});</code></pre>
                </div>
                
                <div class="code-after">
                    <h5>压缩后:</h5>
                    <pre><code>function initLazyLoading(){const a=document.querySelectorAll("img[data-src]"),b=new IntersectionObserver(function(c){c.forEach(function(d){if(d.isIntersecting){const e=d.target;e.src=e.getAttribute("data-src"),e.removeAttribute("data-src"),b.unobserve(e)}})});a.forEach(function(c){b.observe(c)})}document.addEventListener("DOMContentLoaded",function(){initLazyLoading()});</code></pre>
                </div>
            </div>
        </div>
    </div>
    
    <div class="compression-tools">
        <h4>常用压缩工具</h4>
        <div class="tools-grid">
            <div class="tool-item">
                <h5>在线工具</h5>
                <ul>
                    <li>CSS Minifier</li>
                    <li>JavaScript Minifier</li>
                    <li>HTML Minifier</li>
                </ul>
            </div>
            <div class="tool-item">
                <h5>构建工具</h5>
                <ul>
                    <li>Webpack</li>
                    <li>Gulp</li>
                    <li>Parcel</li>
                </ul>
            </div>
            <div class="tool-item">
                <h5>压缩库</h5>
                <ul>
                    <li>UglifyJS</li>
                    <li>Terser</li>
                    <li>cssnano</li>
                </ul>
            </div>
        </div>
    </div>
</div>

<style>
    .css-js-compression {
        max-width: 1200px;
        margin: 0 auto;
        padding: 20px;
    }
    
    .css-js-compression h3 {
        text-align: center;
        color: #333;
        margin-bottom: 30px;
    }
    
    .compression-techniques {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 30px;
        margin-bottom: 40px;
    }
    
    .technique-card {
        background: white;
        padding: 20px;
        border-radius: 12px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    }
    
    .technique-card h4 {
        color: #2c3e50;
        margin: 0 0 20px 0;
        text-align: center;
    }
    
    .code-before,
    .code-after {
        margin-bottom: 20px;
    }
    
    .code-before h5 {
        color: #e74c3c;
        margin: 0 0 10px 0;
    }
    
    .code-after h5 {
        color: #27ae60;
        margin: 0 0 10px 0;
    }
    
    .technique-example pre {
        margin: 0;
        overflow-x: auto;
    }
    
    .technique-example code {
        background: #2c3e50;
        color: #ecf0f1;
        padding: 15px;
        border-radius: 8px;
        display: block;
        font-size: 11px;
        line-height: 1.4;
        white-space: pre-wrap;
    }
    
    .compression-tools {
        background: #f8f9fa;
        padding: 25px;
        border-radius: 12px;
    }
    
    .compression-tools h4 {
        color: #333;
        margin: 0 0 20px 0;
        text-align: center;
    }
    
    .tools-grid {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 20px;
    }
    
    .tool-item {
        background: white;
        padding: 20px;
        border-radius: 8px;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
    
    .tool-item h5 {
        color: #3498db;
        margin: 0 0 15px 0;
        text-align: center;
    }
    
    .tool-item ul {
        list-style: none;
        padding: 0;
        margin: 0;
    }
    
    .tool-item li {
        color: #666;
        padding: 5px 0;
        border-bottom: 1px solid #eee;
    }
    
    .tool-item li:last-child {
        border-bottom: none;
    }
    
    @media (max-width: 768px) {
        .compression-techniques {
            grid-template-columns: 1fr;
        }
        
        .tools-grid {
            grid-template-columns: 1fr;
        }
    }
</style>

11.1.3 缓存策略

HTTP缓存

html
<!-- HTTP缓存策略示例 -->
<div class="http-caching">
    <h3>HTTP缓存策略</h3>
    
    <div class="cache-headers">
        <div class="header-example">
            <h4>Cache-Control头部设置</h4>
            <div class="header-code">
                <pre><code># 静态资源缓存(1年)
Cache-Control: public, max-age=31536000, immutable

# HTML文件缓存策略
Cache-Control: public, max-age=0, must-revalidate

# API响应缓存
Cache-Control: private, max-age=300

# 不缓存敏感数据
Cache-Control: no-cache, no-store, must-revalidate</code></pre>
            </div>
        </div>
        
        <div class="header-example">
            <h4>ETag和Last-Modified</h4>
            <div class="header-code">
                <pre><code># 服务器响应头
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT

# 客户端请求头
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT</code></pre>
            </div>
        </div>
    </div>
    
    <div class="cache-strategy-table">
        <h4>缓存策略参考</h4>
        <table class="strategy-table">
            <thead>
                <tr>
                    <th>资源类型</th>
                    <th>缓存策略</th>
                    <th>缓存时间</th>
                    <th>说明</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>HTML</td>
                    <td>no-cache</td>
                    <td>0</td>
                    <td>每次验证是否更新</td>
                </tr>
                <tr>
                    <td>CSS/JS</td>
                    <td>public, immutable</td>
                    <td>1年</td>
                    <td>使用版本号控制更新</td>
                </tr>
                <tr>
                    <td>图片</td>
                    <td>public</td>
                    <td>30天</td>
                    <td>较长时间缓存</td>
                </tr>
                <tr>
                    <td>字体</td>
                    <td>public, immutable</td>
                    <td>1年</td>
                    <td>很少变化的资源</td>
                </tr>
                <tr>
                    <td>API数据</td>
                    <td>private</td>
                    <td>5-15分钟</td>
                    <td>用户相关数据</td>
                </tr>
            </tbody>
        </table>
    </div>
</div>

<style>
    .http-caching {
        max-width: 1200px;
        margin: 0 auto;
        padding: 20px;
    }
    
    .http-caching h3 {
        text-align: center;
        color: #333;
        margin-bottom: 30px;
    }
    
    .cache-headers {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 30px;
        margin-bottom: 40px;
    }
    
    .header-example {
        background: white;
        padding: 20px;
        border-radius: 12px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    }
    
    .header-example h4 {
        color: #2c3e50;
        margin: 0 0 15px 0;
    }
    
    .header-code pre {
        margin: 0;
        overflow-x: auto;
    }
    
    .header-code code {
        background: #2c3e50;
        color: #ecf0f1;
        padding: 15px;
        border-radius: 8px;
        display: block;
        font-size: 12px;
        line-height: 1.4;
    }
    
    .cache-strategy-table {
        background: #f8f9fa;
        padding: 25px;
        border-radius: 12px;
    }
    
    .cache-strategy-table h4 {
        color: #333;
        margin: 0 0 20px 0;
        text-align: center;
    }
    
    .strategy-table {
        width: 100%;
        border-collapse: collapse;
        background: white;
        border-radius: 8px;
        overflow: hidden;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
    
    .strategy-table th {
        background: #3498db;
        color: white;
        padding: 15px;
        text-align: left;
        font-weight: 600;
    }
    
    .strategy-table td {
        padding: 12px 15px;
        border-bottom: 1px solid #eee;
        color: #333;
    }
    
    .strategy-table tr:last-child td {
        border-bottom: none;
    }
    
    .strategy-table tr:nth-child(even) {
        background: #f8f9fa;
    }
    
    @media (max-width: 768px) {
        .cache-headers {
            grid-template-columns: 1fr;
        }
        
        .strategy-table {
            font-size: 14px;
        }
        
        .strategy-table th,
        .strategy-table td {
            padding: 10px 8px;
        }
    }
</style>

Service Worker缓存

html
<!-- Service Worker缓存示例 -->
<div class="service-worker-cache">
    <h3>Service Worker缓存</h3>
    
    <div class="sw-implementation">
        <div class="sw-code">
            <h4>Service Worker注册</h4>
            <pre><code>// 主线程中注册Service Worker
if ('serviceWorker' in navigator) {
    window.addEventListener('load', function() {
        navigator.serviceWorker.register('/sw.js')
            .then(function(registration) {
                console.log('SW注册成功:', registration.scope);
            })
            .catch(function(error) {
                console.log('SW注册失败:', error);
            });
    });
}</code></pre>
        </div>
        
        <div class="sw-code">
            <h4>Service Worker缓存策略</h4>
            <pre><code>// sw.js - Service Worker文件
const CACHE_NAME = 'app-cache-v1';
const urlsToCache = [
    '/',
    '/styles/main.css',
    '/scripts/main.js',
    '/images/logo.png'
];

// 安装事件 - 缓存资源
self.addEventListener('install', function(event) {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(function(cache) {
                return cache.addAll(urlsToCache);
            })
    );
});

// 网络请求拦截
self.addEventListener('fetch', function(event) {
    event.respondWith(
        caches.match(event.request)
            .then(function(response) {
                // 缓存命中,返回缓存资源
                if (response) {
                    return response;
                }
                
                // 缓存未命中,发起网络请求
                return fetch(event.request)
                    .then(function(response) {
                        // 检查是否是有效响应
                        if (!response || response.status !== 200 || response.type !== 'basic') {
                            return response;
                        }
                        
                        // 克隆响应,因为响应流只能使用一次
                        const responseToCache = response.clone();
                        
                        caches.open(CACHE_NAME)
                            .then(function(cache) {
                                cache.put(event.request, responseToCache);
                            });
                        
                        return response;
                    });
            })
    );
});</code></pre>
        </div>
    </div>
    
    <div class="cache-strategies">
        <h4>缓存策略模式</h4>
        <div class="strategies-grid">
            <div class="strategy-item">
                <h5>Cache First</h5>
                <p>优先从缓存读取,适用于不经常变化的静态资源</p>
                <div class="strategy-code">
                    <pre><code>// 缓存优先策略
caches.match(event.request)
    .then(response => {
        return response || fetch(event.request);
    })</code></pre>
                </div>
            </div>
            
            <div class="strategy-item">
                <h5>Network First</h5>
                <p>优先从网络获取,适用于经常变化的动态内容</p>
                <div class="strategy-code">
                    <pre><code>// 网络优先策略
fetch(event.request)
    .catch(() => {
        return caches.match(event.request);
    })</code></pre>
                </div>
            </div>
            
            <div class="strategy-item">
                <h5>Stale While Revalidate</h5>
                <p>返回缓存的同时更新缓存,适用于可接受过期数据的场景</p>
                <div class="strategy-code">
                    <pre><code>// 过期重新验证策略
const cachedResponse = caches.match(event.request);
const fetchPromise = fetch(event.request).then(response => {
    cache.put(event.request, response.clone());
    return response;
});
return cachedResponse || fetchPromise;</code></pre>
                </div>
            </div>
        </div>
    </div>
</div>

<style>
    .service-worker-cache {
        max-width: 1200px;
        margin: 0 auto;
        padding: 20px;
    }
    
    .service-worker-cache h3 {
        text-align: center;
        color: #333;
        margin-bottom: 30px;
    }
    
    .sw-implementation {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 30px;
        margin-bottom: 40px;
    }
    
    .sw-code {
        background: white;
        padding: 20px;
        border-radius: 12px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    }
    
    .sw-code h4 {
        color: #2c3e50;
        margin: 0 0 15px 0;
    }
    
    .sw-code pre {
        margin: 0;
        overflow-x: auto;
    }
    
    .sw-code code {
        background: #2c3e50;
        color: #ecf0f1;
        padding: 15px;
        border-radius: 8px;
        display: block;
        font-size: 11px;
        line-height: 1.4;
    }
    
    .cache-strategies {
        background: #f8f9fa;
        padding: 25px;
        border-radius: 12px;
    }
    
    .cache-strategies h4 {
        color: #333;
        margin: 0 0 20px 0;
        text-align: center;
    }
    
    .strategies-grid {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 20px;
    }
    
    .strategy-item {
        background: white;
        padding: 20px;
        border-radius: 8px;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
    
    .strategy-item h5 {
        color: #3498db;
        margin: 0 0 10px 0;
    }
    
    .strategy-item p {
        color: #666;
        font-size: 14px;
        margin: 0 0 15px 0;
        line-height: 1.5;
    }
    
    .strategy-code pre {
        margin: 0;
        overflow-x: auto;
    }
    
    .strategy-code code {
        background: #2c3e50;
        color: #ecf0f1;
        padding: 10px;
        border-radius: 4px;
        display: block;
        font-size: 10px;
        line-height: 1.3;
    }
    
    @media (max-width: 768px) {
        .sw-implementation {
            grid-template-columns: 1fr;
        }
        
        .strategies-grid {
            grid-template-columns: 1fr;
        }
    }
</style>

11.1.4 延迟加载和预加载技术

延迟加载实现

html
<!-- 延迟加载实现示例 -->
<div class="lazy-loading-implementation">
    <h3>延迟加载技术</h3>
    
    <div class="lazy-examples">
        <div class="image-lazy-loading">
            <h4>图片延迟加载</h4>
            <div class="image-gallery">
                <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='200'%3E%3Crect width='100%25' height='100%25' fill='%23f0f0f0'/%3E%3Ctext x='50%25' y='50%25' text-anchor='middle' dy='.3em' fill='%23666'%3E加载中...%3C/text%3E%3C/svg%3E"
                     data-src="https://picsum.photos/300/200?random=1"
                     class="lazy-img"
                     alt="延迟加载图片1">
                     
                <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='200'%3E%3Crect width='100%25' height='100%25' fill='%23f0f0f0'/%3E%3Ctext x='50%25' y='50%25' text-anchor='middle' dy='.3em' fill='%23666'%3E加载中...%3C/text%3E%3C/svg%3E"
                     data-src="https://picsum.photos/300/200?random=2"
                     class="lazy-img"
                     alt="延迟加载图片2">
                     
                <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='200'%3E%3Crect width='100%25' height='100%25' fill='%23f0f0f0'/%3E%3Ctext x='50%25' y='50%25' text-anchor='middle' dy='.3em' fill='%23666'%3E加载中...%3C/text%3E%3C/svg%3E"
                     data-src="https://picsum.photos/300/200?random=3"
                     class="lazy-img"
                     alt="延迟加载图片3">
            </div>
            
            <div class="lazy-code">
                <h5>实现代码:</h5>
                <pre><code>// Intersection Observer API实现图片懒加载
function initImageLazyLoading() {
    const images = document.querySelectorAll('img[data-src]');
    
    if ('IntersectionObserver' in window) {
        const imageObserver = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const img = entry.target;
                    img.src = img.dataset.src;
                    img.removeAttribute('data-src');
                    img.classList.add('loaded');
                    imageObserver.unobserve(img);
                }
            });
        }, {
            rootMargin: '50px 0px',
            threshold: 0.01
        });
        
        images.forEach(img => imageObserver.observe(img));
    } else {
        // 降级方案:直接加载所有图片
        images.forEach(img => {
            img.src = img.dataset.src;
            img.removeAttribute('data-src');
        });
    }
}

// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', initImageLazyLoading);</code></pre>
            </div>
        </div>
        
        <div class="content-lazy-loading">
            <h4>内容延迟加载</h4>
            <div class="content-sections">
                <div class="content-section visible">
                    <h5>首屏内容</h5>
                    <p>这是首屏显示的内容,会立即加载。</p>
                </div>
                
                <div class="content-section" data-lazy-content>
                    <div class="loading-placeholder">
                        <div class="loading-spinner"></div>
                        <span>内容加载中...</span>
                    </div>
                </div>
                
                <div class="content-section" data-lazy-content>
                    <div class="loading-placeholder">
                        <div class="loading-spinner"></div>
                        <span>内容加载中...</span>
                    </div>
                </div>
            </div>
            
            <div class="lazy-code">
                <h5>内容延迟加载实现:</h5>
                <pre><code>// 内容延迟加载
function initContentLazyLoading() {
    const sections = document.querySelectorAll('[data-lazy-content]');
    
    const sectionObserver = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                loadSectionContent(entry.target);
                sectionObserver.unobserve(entry.target);
            }
        });
    }, {
        rootMargin: '100px 0px',
        threshold: 0.1
    });
    
    sections.forEach(section => sectionObserver.observe(section));
}

function loadSectionContent(section) {
    // 模拟异步加载内容
    setTimeout(() => {
        section.innerHTML = `
            &lt;h5&gt;延迟加载的内容&lt;/h5&gt;
            &lt;p&gt;这部分内容在用户滚动到附近时才加载,提高了页面初始加载速度。&lt;/p&gt;
        `;
        section.classList.add('loaded');
    }, 1000);
}</code></pre>
            </div>
        </div>
    </div>
</div>

<style>
    .lazy-loading-implementation {
        max-width: 1200px;
        margin: 0 auto;
        padding: 20px;
    }
    
    .lazy-loading-implementation h3 {
        text-align: center;
        color: #333;
        margin-bottom: 30px;
    }
    
    .lazy-examples {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 40px;
    }
    
    .image-lazy-loading,
    .content-lazy-loading {
        background: white;
        padding: 25px;
        border-radius: 12px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    }
    
    .image-lazy-loading h4,
    .content-lazy-loading h4 {
        color: #2c3e50;
        margin: 0 0 20px 0;
        text-align: center;
    }
    
    .image-gallery {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 15px;
        margin-bottom: 20px;
    }
    
    .lazy-img {
        width: 100%;
        height: 120px;
        object-fit: cover;
        border-radius: 8px;
        transition: opacity 0.3s ease;
        opacity: 0.7;
    }
    
    .lazy-img.loaded {
        opacity: 1;
    }
    
    .content-sections {
        margin-bottom: 20px;
    }
    
    .content-section {
        background: #f8f9fa;
        padding: 20px;
        border-radius: 8px;
        margin-bottom: 15px;
        min-height: 80px;
    }
    
    .content-section.visible {
        background: #e3f2fd;
    }
    
    .content-section.loaded {
        background: #e8f5e8;
        animation: fadeIn 0.5s ease;
    }
    
    .loading-placeholder {
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 10px;
        color: #666;
    }
    
    .loading-spinner {
        width: 20px;
        height: 20px;
        border: 2px solid #f3f3f3;
        border-top: 2px solid #3498db;
        border-radius: 50%;
        animation: spin 1s linear infinite;
    }
    
    @keyframes spin {
        0% { transform: rotate(0deg); }
        100% { transform: rotate(360deg); }
    }
    
    @keyframes fadeIn {
        from { opacity: 0; transform: translateY(20px); }
        to { opacity: 1; transform: translateY(0); }
    }
    
    .lazy-code {
        background: #2c3e50;
        border-radius: 8px;
        overflow: hidden;
    }
    
    .lazy-code h5 {
        color: #ecf0f1;
        margin: 0;
        padding: 15px 15px 0;
    }
    
    .lazy-code pre {
        margin: 0;
        overflow-x: auto;
    }
    
    .lazy-code code {
        color: #ecf0f1;
        padding: 15px;
        display: block;
        font-size: 11px;
        line-height: 1.4;
    }
    
    @media (max-width: 768px) {
        .lazy-examples {
            grid-template-columns: 1fr;
        }
        
        .image-gallery {
            grid-template-columns: 1fr;
        }
    }
</style>

<script>
// 图片懒加载实现
function initImageLazyLoading() {
    const images = document.querySelectorAll('img[data-src]');
    
    if ('IntersectionObserver' in window) {
        const imageObserver = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const img = entry.target;
                    img.src = img.dataset.src;
                    img.removeAttribute('data-src');
                    img.classList.add('loaded');
                    imageObserver.unobserve(img);
                }
            });
        }, {
            rootMargin: '50px 0px',
            threshold: 0.01
        });
        
        images.forEach(img => imageObserver.observe(img));
    } else {
        // 降级方案
        images.forEach(img => {
            img.src = img.dataset.src;
            img.removeAttribute('data-src');
        });
    }
}

// 内容懒加载实现
function initContentLazyLoading() {
    const sections = document.querySelectorAll('[data-lazy-content]');
    
    const sectionObserver = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                loadSectionContent(entry.target);
                sectionObserver.unobserve(entry.target);
            }
        });
    }, {
        rootMargin: '100px 0px',
        threshold: 0.1
    });
    
    sections.forEach(section => sectionObserver.observe(section));
}

function loadSectionContent(section) {
    setTimeout(() => {
        section.innerHTML = `
            <h5>延迟加载的内容</h5>
            <p>这部分内容在用户滚动到附近时才加载,提高了页面初始加载速度。</p>
        `;
        section.classList.add('loaded');
    }, 1000);
}

// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
    initImageLazyLoading();
    initContentLazyLoading();
});
</script>

预加载技术

html
<!-- 预加载技术示例 -->
<div class="preloading-techniques">
    <h3>预加载技术</h3>
    
    <div class="preload-methods">
        <div class="method-card">
            <h4>DNS预解析</h4>
            <div class="method-example">
                <pre><code>&lt;!-- DNS预解析 --&gt;
&lt;link rel="dns-prefetch" href="//fonts.googleapis.com"&gt;
&lt;link rel="dns-prefetch" href="//api.example.com"&gt;
&lt;link rel="dns-prefetch" href="//cdn.example.com"&gt;</code></pre>
            </div>
            <p>提前解析域名,减少DNS查询时间。</p>
        </div>
        
        <div class="method-card">
            <h4>预连接</h4>
            <div class="method-example">
                <pre><code>&lt;!-- 预连接 --&gt;
&lt;link rel="preconnect" href="https://fonts.googleapis.com"&gt;
&lt;link rel="preconnect" href="https://api.example.com"&gt;</code></pre>
            </div>
            <p>提前建立连接,包括DNS解析、TCP握手和TLS协商。</p>
        </div>
        
        <div class="method-card">
            <h4>资源预加载</h4>
            <div class="method-example">
                <pre><code>&lt;!-- 预加载关键资源 --&gt;
&lt;link rel="preload" href="critical.css" as="style"&gt;
&lt;link rel="preload" href="hero-image.jpg" as="image"&gt;
&lt;link rel="preload" href="main-font.woff2" as="font" type="font/woff2" crossorigin&gt;</code></pre>
            </div>
            <p>预加载当前页面需要的关键资源。</p>
        </div>
        
        <div class="method-card">
            <h4>页面预取</h4>
            <div class="method-example">
                <pre><code>&lt;!-- 预取下一页面的资源 --&gt;
&lt;link rel="prefetch" href="/next-page.html"&gt;
&lt;link rel="prefetch" href="/images/next-page-hero.jpg"&gt;</code></pre>
            </div>
            <p>预取用户可能访问的下一个页面资源。</p>
        </div>
    </div>
    
    <div class="preload-javascript">
        <h4>JavaScript预加载实现</h4>
        <div class="js-preload-example">
            <pre><code>// JavaScript预加载实现
class ResourcePreloader {
    constructor() {
        this.preloadQueue = new Map();
        this.loadedResources = new Set();
    }
    
    // 预加载图片
    preloadImage(src, priority = 'low') {
        if (this.loadedResources.has(src)) {
            return Promise.resolve();
        }
        
        if (this.preloadQueue.has(src)) {
            return this.preloadQueue.get(src);
        }
        
        const promise = new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => {
                this.loadedResources.add(src);
                resolve(img);
            };
            img.onerror = reject;
            img.src = src;
        });
        
        this.preloadQueue.set(src, promise);
        return promise;
    }
    
    // 预加载CSS
    preloadCSS(href) {
        if (this.loadedResources.has(href)) {
            return Promise.resolve();
        }
        
        const promise = new Promise((resolve, reject) => {
            const link = document.createElement('link');
            link.rel = 'preload';
            link.as = 'style';
            link.href = href;
            link.onload = () => {
                this.loadedResources.add(href);
                resolve();
            };
            link.onerror = reject;
            document.head.appendChild(link);
        });
        
        this.preloadQueue.set(href, promise);
        return promise;
    }
    
    // 预加载脚本
    preloadScript(src) {
        if (this.loadedResources.has(src)) {
            return Promise.resolve();
        }
        
        const promise = new Promise((resolve, reject) => {
            const link = document.createElement('link');
            link.rel = 'preload';
            link.as = 'script';
            link.href = src;
            link.onload = () => {
                this.loadedResources.add(src);
                resolve();
            };
            link.onerror = reject;
            document.head.appendChild(link);
        });
        
        this.preloadQueue.set(src, promise);
        return promise;
    }
    
    // 智能预加载:基于用户行为
    intelligentPreload() {
        // 监听鼠标悬停事件
        document.addEventListener('mouseover', (e) => {
            const link = e.target.closest('a[href]');
            if (link && link.hostname === location.hostname) {
                this.preloadPage(link.href);
            }
        });
        
        // 监听链接可见性
        if ('IntersectionObserver' in window) {
            const linkObserver = new IntersectionObserver((entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        const link = entry.target;
                        if (link.hostname === location.hostname) {
                            this.preloadPage(link.href);
                        }
                    }
                });
            }, { rootMargin: '200px' });
            
            document.querySelectorAll('a[href]').forEach(link => {
                linkObserver.observe(link);
            });
        }
    }
    
    // 预加载页面
    preloadPage(url) {
        if (this.loadedResources.has(url)) {
            return;
        }
        
        const link = document.createElement('link');
        link.rel = 'prefetch';
        link.href = url;
        document.head.appendChild(link);
        this.loadedResources.add(url);
    }
}

// 使用示例
const preloader = new ResourcePreloader();

// 预加载关键资源
preloader.preloadImage('/images/hero.jpg')
    .then(() => console.log('英雄图片预加载完成'));

preloader.preloadCSS('/styles/non-critical.css');

// 启用智能预加载
preloader.intelligentPreload();</code></pre>
        </div>
    </div>
</div>

<style>
    .preloading-techniques {
        max-width: 1200px;
        margin: 0 auto;
        padding: 20px;
    }
    
    .preloading-techniques h3 {
        text-align: center;
        color: #333;
        margin-bottom: 30px;
    }
    
    .preload-methods {
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        gap: 20px;
        margin-bottom: 40px;
    }
    
    .method-card {
        background: white;
        padding: 20px;
        border-radius: 12px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
        border-left: 4px solid #3498db;
    }
    
    .method-card h4 {
        color: #2c3e50;
        margin: 0 0 15px 0;
    }
    
    .method-example {
        background: #f8f9fa;
        border-radius: 8px;
        margin-bottom: 15px;
        overflow: hidden;
    }
    
    .method-example pre {
        margin: 0;
        overflow-x: auto;
    }
    
    .method-example code {
        background: #2c3e50;
        color: #ecf0f1;
        padding: 15px;
        display: block;
        font-size: 11px;
        line-height: 1.4;
    }
    
    .method-card p {
        color: #666;
        margin: 0;
        font-size: 14px;
        line-height: 1.5;
    }
    
    .preload-javascript {
        background: #f8f9fa;
        padding: 25px;
        border-radius: 12px;
    }
    
    .preload-javascript h4 {
        color: #333;
        margin: 0 0 20px 0;
        text-align: center;
    }
    
    .js-preload-example {
        background: #2c3e50;
        border-radius: 8px;
        overflow: hidden;
    }
    
    .js-preload-example pre {
        margin: 0;
        overflow-x: auto;
    }
    
    .js-preload-example code {
        color: #ecf0f1;
        padding: 20px;
        display: block;
        font-size: 11px;
        line-height: 1.4;
    }
    
    @media (max-width: 768px) {
        .preload-methods {
            grid-template-columns: 1fr;
        }
    }
</style>

本节要点回顾

  • 关键渲染路径:理解浏览器渲染过程,优化关键资源的加载顺序和数量
  • 资源压缩:使用HTML、CSS、JavaScript压缩技术,显著减少文件大小和传输时间
  • 缓存策略:合理设置HTTP缓存和Service Worker缓存,提高资源重复访问效率
  • 延迟加载:非关键资源延迟加载,优先保证首屏内容快速显示
  • 预加载技术:使用各种预加载策略,提前准备用户可能需要的资源

相关学习资源

常见问题FAQ

Q: 什么是关键渲染路径,为什么要优化它?

A: 关键渲染路径是浏览器显示首屏内容所需的最小资源集合。优化它可以显著提升首屏渲染时间,改善用户体验。

Q: 如何选择合适的缓存策略?

A: 根据资源特性选择:静态资源使用长期缓存;动态内容使用短期缓存或不缓存;API数据根据更新频率设置合适的缓存时间。

Q: 延迟加载会影响SEO吗?

A: 正确实现的延迟加载不会影响SEO。确保为图片提供alt属性,为延迟加载的内容提供合适的占位符,必要时提供noscript降级方案。

Q: 什么时候使用预加载,什么时候使用预取?

A: 预加载(preload)用于当前页面的关键资源;预取(prefetch)用于下一个页面可能需要的资源。预加载优先级高,预取优先级低。

Q: Service Worker缓存和HTTP缓存有什么区别?

A: HTTP缓存由浏览器自动管理,缓存策略相对固定;Service Worker缓存由开发者编程控制,可以实现更复杂的缓存策略和离线功能。


下一节预览:下一节我们将学习代码优化,重点介绍HTML代码优化、标签使用最佳实践、文档结构优化、无用代码清理和代码压缩的具体方法。