摘要: 用户感知的加载速度,很大程度上取决于资源如何被下载、解析和执行。优化资源加载是性能提升中最直接有效的一环,直接影响核心性能指标。
1. 关键渲染路径(Critical Rendering Path)优化
关键渲染路径是浏览器将HTML、CSS和JavaScript转换为像素 on 屏幕所经过的步骤序列。
优化策略:
-
精简和压缩资源
# 使用工具压缩CSS/JS npm install -g uglify-js clean-css # 压缩JavaScript uglifyjs source.js -o output.min.js -c -m # 压缩CSS cleancss -o style.min.css style.css -
开启Gzip/Brotli压缩
# Nginx配置示例 gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml; gzip_min_length 1000; gzip_comp_level 6; # 更高效的Brotli压缩(需要单独模块) brotli on; brotli_types text/plain text/css application/json application/javascript text/xml; brotli_comp_level 6; -
内联关键CSS(Critical CSS)
<!DOCTYPE html> <html> <head> <style> /* 内联首屏内容所需的关键CSS */ .header { background: #333; color: white; } .hero { font-size: 2rem; margin: 2rem 0; } /* 其余非关键CSS异步加载 */ </style> <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> </head> <body> <!-- 页面内容 --> <script> // 异步加载CSS的降级方案 !function(e){"use strict" var n=function(n,t,o){function i(e){return f.body?e():void setTimeout(function(){i(e)})}var d=!1,r=function(){if(!d){d=!0;var e=t.createElement("link");e.rel="stylesheet",e.href=n,o&&(e.media="only x"),t.head.appendChild(e)}};i(r)};e.loadCSS=n}(this); loadCSS("non-critical.css"); </script> </body> </html>
2. async 与 defer 属性的深入理解
这两个属性都能实现脚本的异步加载,但执行时机有重要区别。
执行时机对比:
<!-- 常规脚本:阻塞HTML解析 -->
<script src="script.js"></script>
<!-- async:下载不阻塞,但下载完立即执行(执行时可能阻塞) -->
<script async src="async-script.js"></script>
<!-- defer:下载不阻塞,在DOMContentLoaded前按顺序执行 -->
<script defer src="defer-script.js"></script>
可视化执行流程:
text
HTML解析开始
↓
遇到 <script> → 下载 → 执行 → 继续解析
↓
遇到 <script async> → 异步下载 → 下载完成立即执行(可能中断解析)
↓
遇到 <script defer> → 异步下载 → 等待解析完成 → 按顺序执行
↓
DOMContentLoaded
最佳实践示例:
<!-- 分析工具、广告脚本等无依赖的脚本 -->
<script async src="https://www.google-analytics.com/analytics.js"></script>
<!-- 有依赖关系或操作DOM的脚本 -->
<script defer src="vendor.js"></script>
<script defer src="app.js"></script>
<!-- 需要立即执行的内联脚本 -->
<script>
// 设置全局变量或立即需要的功能
window.APP_CONFIG = { apiUrl: '/api' };
</script>
3. 现代图片优化技术
图片格式选择指南:
<!-- WebP现代格式(优先) -->
<picture>
<source srcset="image.webp" type="image/webp">
<source srcset="image.jpg" type="image/jpeg">
<img src="image.jpg" alt="描述">
</picture>
<!-- SVG矢量图标 -->
<svg width="100" height="100">
<circle cx="50" cy="50" r="40" fill="#ff0000"/>
</svg>
<!-- 响应式图片 -->
<img
srcset="image-320w.jpg 320w,
image-480w.jpg 480w,
image-800w.jpg 800w"
sizes="(max-width: 320px) 280px,
(max-width: 480px) 440px,
800px"
src="image-800w.jpg"
alt="响应式图片">
图片懒加载实现:
<!-- 原生懒加载(现代浏览器) -->
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy" alt="图片">
<!-- 兼容性更好的懒加载 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const lazyImages = [].slice.call(document.querySelectorAll('img.lazy'));
if ('IntersectionObserver' in window) {
const lazyImageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
const lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
lazyImage.classList.remove('lazy');
lazyImageObserver.unobserve(lazyImage);
}
});
});
lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage);
});
} else {
// 回退方案:直接加载所有图片
lazyImages.forEach(function(lazyImage) {
lazyImage.src = lazyImage.dataset.src;
});
}
});
</script>
4. 深入理解浏览器缓存策略
HTTP缓存头配置详解:
# 静态资源(强缓存)
location ~* .(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
# 设置1年强缓存(文件名带哈希时可放心设置)
expires 1y;
add_header Cache-Control "public, immutable, max-age=31536000";
# 添加ETag进行协商缓存
etag on;
}
# HTML文件(协商缓存)
location ~* .html$ {
# 禁用强缓存,使用协商缓存
expires 0;
add_header Cache-Control "no-cache, must-revalidate";
etag on;
}
# API响应(通常不缓存或短时间缓存)
location /api/ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
expires 0;
}
Service Worker缓存策略:
// sw.js - Service Worker缓存示例
const CACHE_NAME = 'v1.0.0';
const urlsToCache = [
'/',
'/styles/main.css',
'/scripts/app.js',
'/images/logo.png'
];
// 安装阶段:缓存核心资源
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
);
});
// 拦截请求:网络优先,失败时使用缓存
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request)
.then(response => {
// 网络请求成功,更新缓存
const responseClone = response.clone();
caches.open(CACHE_NAME)
.then(cache => cache.put(event.request, responseClone));
return response;
})
.catch(() => {
// 网络失败,使用缓存
return caches.match(event.request);
})
);
});
5. 资源预加载与优先级控制
预加载关键资源:
<!-- 预加载关键字体 -->
<link rel="preload" href="fonts/important.woff2" as="font" type="font/woff2" crossorigin>
<!-- 预加载关键图片 -->
<link rel="preload" href="images/hero-banner.jpg" as="image">
<!-- 预加载CSS -->
<link rel="preload" href="styles/critical.css" as="style" onload="this.rel='stylesheet'">
<!-- 预加载JavaScript -->
<link rel="preload" href="scripts/critical.js" as="script">
<!-- DNS预连接 -->
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://cdn.example.com">
<!-- 预获取下一页资源 -->
<link rel="prefetch" href="next-page.html">
<link rel="prerender" href="https://example.com/next-page">
使用Resource Hints的现代方法:
// 动态添加预加载
function preloadResource(url, as) {
const link = document.createElement('link');
link.rel = 'preload';
link.href = url;
link.as = as;
document.head.appendChild(link);
}
// 在需要时预加载资源
if (isUserLikelyToVisitCheckout()) {
preloadResource('/checkout.css', 'style');
preloadResource('/checkout.js', 'script');
}
6. 现代打包优化策略
Webpack代码分割示例:
// 动态导入实现路由级代码分割
const ProductPage = lazy(() => import(
/* webpackChunkName: "product" */ './pages/ProductPage'
));
const CheckoutPage = lazy(() => import(
/* webpackChunkName: "checkout" */ './pages/CheckoutPage'
));
// 组件级代码分割
const HeavyComponent = lazy(() => import(
/* webpackPrefetch: true */ './components/HeavyComponent'
));
// Webpack配置优化
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\/]node_modules[\/]/,
name: 'vendors',
chunks: 'all',
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
enforce: true
}
}
}
}
};
7. 性能监控与自适应加载
网络状况检测:
// 检测网络状况
function getNetworkInfo() {
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
if (connection) {
return {
effectiveType: connection.effectiveType, // '4g', '3g', '2g'
downlink: connection.downlink, // Mbps
rtt: connection.rtt, // 往返时间
saveData: connection.saveData // 节流模式
};
}
return null;
}
// 根据网络状况调整资源加载
function loadAppropriateResources() {
const networkInfo = getNetworkInfo();
if (networkInfo && (networkInfo.effectiveType === '2g' || networkInfo.saveData)) {
// 低速网络:加载轻量版资源
loadLightVersion();
} else {
// 高速网络:加载完整资源
loadFullVersion();
}
}
// 图片质量自适应
function getImageQuality() {
const networkInfo = getNetworkInfo();
if (networkInfo && networkInfo.effectiveType === '2g') {
return 'low'; // 低质量图片
} else if (networkInfo && networkInfo.effectiveType === '3g') {
return 'medium'; // 中等质量
} else {
return 'high'; // 高质量
}
}
8. CDN与边缘计算优化
智能CDN策略:
// 动态选择最近的CDN节点
function getOptimalCDNUrl(resourcePath) {
const userRegion = getUserRegion(); // 通过IP或浏览器API获取
const cdnDomains = {
'na': 'cdn-na.example.com',
'eu': 'cdn-eu.example.com',
'asia': 'cdn-asia.example.com'
};
const domain = cdnDomains[userRegion] || cdnDomains['na'];
return `https://${domain}${resourcePath}`;
}
// 使用边缘计算进行A/B测试
async function getPersonalizedContent(userId) {
// 在CDN边缘节点处理个性化逻辑
const response = await fetch('/api/personalized', {
headers: {
'X-User-ID': userId,
'X-Edge-Compute': 'true'
}
});
return response.json();
}
总结:资源加载优化的核心思想是 "在正确的时间,以正确的优先级,加载正确的资源" 。通过合理的缓存策略、资源预加载、代码分割和网络自适应,可以显著提升应用的加载性能和用户体验。记住,在慢速网络上的性能提升往往比在高速网络上更加明显,也更能体现优化的价值。