前端资源优化
相关资料
- Cache-Control header:developer.mozilla.org/en-US/docs/…
- Last-Modified header:developer.mozilla.org/en-US/docs/…
- ETag header:developer.mozilla.org/en-US/docs/…
- IndexedDB:developer.mozilla.org/en-US/docs/…
- Dexie.js:github.com/dexie/Dexie…
- Service Worker API:developer.mozilla.org/en-US/docs/…
- Workbox:developer.chrome.com/docs/workbo…
- 字体子集化fontmin:github.com/ecomfe/font…
- font-spider:github.com/aui/font-sp…
- 预加载和预请求:developer.mozilla.org/en-US/docs/…
- PWA渐进式应用:web.dev/explore/pro…
- 浏览器请求和响应的压缩:nodejs.org/api/zlib.ht…
相关问题
请说说你做过的关于资源方面的优化?
-
资源压缩:使用工具如 uglify-js、CSSNano 对 JavaScript 和 CSS文件进行压缩,以减少文件体积。图片则采用 imagemin 进行压缩优化。
-
文件合并:通过 Webpack 等构建工具将多个 CsS和JS文件合并,减少 HTTP 请求数量。
-
资源懒加载与异步加载:对于非关键资源(如图片和次要 JavaScript 文件),我采用了懒加载策略,只有在需要时才加载这些资源。同时,关键的JS 脚本采用异步加载(如 async 或 defer),以提高页面的首屏渲染速度。
-
字体优化:使用字体子集化工具(如 Fontmin)减少字体文件的体积,并使用 woff2 格式的字体文件以获得更好的压缩效果。
-
缓存策略:配置 Cache-Control、ETag 和 Last-Modified 等 HTTP 头来优化浏览器缓存,并使用 Service Worker 进行更细粒度的缓存控制。对于长时间不变的资源,设置了较长的缓存时间。
-
预加载与预请求:使用 link 标签的 rel="preload" 和 rel="prefetch"属性来预加载和预请求关键资源,从而提升页面加载速度。
-
CDN:将静态资源部署到 CDN(内容分发网络)上,利用其多节点分布的优势,降低请求延迟。
前端如果想本地化存储数据,有什么方案?
-
LocalStorage:一种同步 API,用于存储少量的键值对数据(通常不超过 5MB)。数据没有过期时间,除非手动删除,否则会一直存在。
-
Sessionstorage:与 Localstorage类似,但数据仅在页面会话期间存储,即浏览器窗口关闭后数据会被清除。
-
IndexedDB:一种低级 API,提供了更大的存储空间和更强的查询能力,适用于需要存储结构化数据或大量数据的场景。IndexedDB 是异步的,性能较好,适合复杂的存储需求。
-
Cookies:用于在客户端存储少量数据,并且可以在客户端与服务器之间传递。通常用于会话管理,但由于大小限制(约 4KB)和安全问题,不推荐大量使用。
-
Service Worker Cache:通过 Service Worker,可以在浏览器缓存中存储网络请求的结果,便于离线访问和提升页面加载性能。适用于 PWA(渐进式 Web 应用)场景。
资源压缩和请求优化
资源压缩
JavaScript 和 CSS 文件压缩
使用 Terser 压缩 JavaScript 文件
Terser 是目前最流行的 JavaScript 压缩工具,能够有效减少文件体积并提升加载性能。
安装和基本配置:
// 安装 Terser
// npm install terser --save-dev
const { minify } = require('terser');
const fs = require('fs');
// 基础压缩配置
const terserOptions = {
compress: {
// 删除 console.log
drop_console: true,
// 删除 debugger
drop_debugger: true,
// 删除未使用的变量
unused: true,
// 优化 if 语句
conditionals: true,
// 移除死代码
dead_code: true
},
mangle: {
// 混淆变量名
toplevel: true,
// 保留特定名称
reserved: ['$', 'jQuery']
},
format: {
// 移除注释
comments: false
}
};
// 压缩单个文件
async function compressJavaScript(inputPath, outputPath) {
try {
const code = fs.readFileSync(inputPath, 'utf8');
const result = await minify(code, terserOptions);
fs.writeFileSync(outputPath, result.code);
const originalSize = Buffer.byteLength(code, 'utf8');
const compressedSize = Buffer.byteLength(result.code, 'utf8');
const ratio = ((originalSize - compressedSize) / originalSize * 100).toFixed(2);
console.log(`压缩完成: ${inputPath}`);
console.log(`原始大小: ${originalSize} bytes`);
console.log(`压缩后大小: ${compressedSize} bytes`);
console.log(`压缩率: ${ratio}%`);
} catch (error) {
console.error('压缩失败:', error);
}
}
// Webpack 集成配置
const TerserWebpackPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimize: true,
minimizer: [
new TerserWebpackPlugin({
terserOptions: {
compress: {
drop_console: process.env.NODE_ENV === 'production',
drop_debugger: true,
pure_funcs: ['console.log', 'console.info']
},
mangle: {
safari10: true
}
},
extractComments: false
})
]
}
};
使用 cssnano 压缩 CSS 文件
cssnano 是一个模块化的 CSS 压缩工具,通过多种优化策略来减少 CSS 文件体积。
// 安装 cssnano
// npm install cssnano postcss --save-dev
const postcss = require('postcss');
const cssnano = require('cssnano');
const fs = require('fs');
// cssnano 配置选项
const cssnanoOptions = {
preset: ['default', {
// 丢弃注释
discardComments: {
removeAll: true
},
// 规范化空白
normalizeWhitespace: true,
// 合并规则
mergeRules: true,
// 压缩颜色值
colormin: true,
// 优化字体权重
normalizeUnicode: true,
// 删除未使用的 at-rules
discardUnused: true
}]
};
// 压缩 CSS 文件
async function compressCss(inputPath, outputPath) {
try {
const css = fs.readFileSync(inputPath, 'utf8');
const result = await postcss([
cssnano(cssnanoOptions)
]).process(css, { from: inputPath, to: outputPath });
fs.writeFileSync(outputPath, result.css);
if (result.map) {
fs.writeFileSync(outputPath + '.map', result.map.toString());
}
const originalSize = Buffer.byteLength(css, 'utf8');
const compressedSize = Buffer.byteLength(result.css, 'utf8');
const ratio = ((originalSize - compressedSize) / originalSize * 100).toFixed(2);
console.log(`CSS 压缩完成: ${inputPath}`);
console.log(`压缩率: ${ratio}%`);
} catch (error) {
console.error('CSS 压缩失败:', error);
}
}
// PostCSS 配置文件 (postcss.config.js)
module.exports = {
plugins: [
require('autoprefixer'),
...(process.env.NODE_ENV === 'production' ? [
require('cssnano')({
preset: ['default', {
discardComments: { removeAll: true },
normalizeWhitespace: true
}]
})
] : [])
]
};
图片压缩
使用 cwebp 将图片转换为 WebP 格式
WebP 格式能够在保持图片质量的同时显著减少文件大小,相比 JPEG 可节省 25-50% 的体积。
// 安装 imagemin 和相关插件
// npm install imagemin imagemin-webp imagemin-mozjpeg imagemin-pngquant --save-dev
const imagemin = require('imagemin');
const imageminWebp = require('imagemin-webp');
const imageminMozjpeg = require('imagemin-mozjpeg');
const imageminPngquant = require('imagemin-pngquant');
const fs = require('fs');
const path = require('path');
// 图片压缩配置
const imageCompressionConfig = {
webp: {
quality: 80,
method: 6,
autoFilter: true
},
jpeg: {
quality: 85,
progressive: true
},
png: {
quality: [0.6, 0.8],
speed: 1
}
};
// 批量压缩图片并转换为 WebP
async function compressImages(inputDir, outputDir) {
try {
// 压缩并转换为 WebP
const webpFiles = await imagemin([`${inputDir}/*.{jpg,jpeg,png}`], {
destination: `${outputDir}/webp`,
plugins: [
imageminWebp(imageCompressionConfig.webp)
]
});
// 压缩原格式图片作为回退
const fallbackFiles = await imagemin([`${inputDir}/*.{jpg,jpeg,png}`], {
destination: `${outputDir}/fallback`,
plugins: [
imageminMozjpeg(imageCompressionConfig.jpeg),
imageminPngquant(imageCompressionConfig.png)
]
});
console.log('图片压缩完成:');
console.log(`WebP 文件: ${webpFiles.length} 个`);
console.log(`回退文件: ${fallbackFiles.length} 个`);
return { webpFiles, fallbackFiles };
} catch (error) {
console.error('图片压缩失败:', error);
}
}
// 生成响应式图片 HTML
function generateResponsiveImageHtml(imageName, alt = '') {
const baseName = path.parse(imageName).name;
return `
<picture>
<source srcset="/images/webp/${baseName}.webp" type="image/webp">
<source srcset="/images/fallback/${imageName}" type="image/jpeg">
<img src="/images/fallback/${imageName}" alt="${alt}" loading="lazy">
</picture>`;
}
// 实用的图片优化工具函数
class ImageOptimizer {
constructor(options = {}) {
this.quality = options.quality || 80;
this.outputDir = options.outputDir || './dist/images';
}
async processDirectory(inputDir) {
const files = fs.readdirSync(inputDir);
const imageFiles = files.filter(file =>
/\.(jpg|jpeg|png|gif)$/i.test(file)
);
const results = await Promise.all(
imageFiles.map(file => this.processImage(path.join(inputDir, file)))
);
return results;
}
async processImage(inputPath) {
const startTime = Date.now();
const originalStats = fs.statSync(inputPath);
const result = await imagemin([inputPath], {
destination: this.outputDir,
plugins: [
imageminWebp({ quality: this.quality }),
imageminMozjpeg({ quality: this.quality }),
imageminPngquant({ quality: [0.6, 0.8] })
]
});
const processTime = Date.now() - startTime;
const compressionRatio = (
(originalStats.size - result[0].data.length) / originalStats.size * 100
).toFixed(2);
return {
file: path.basename(inputPath),
originalSize: originalStats.size,
compressedSize: result[0].data.length,
compressionRatio,
processTime
};
}
}
文本文件压缩
Node API 来处理 gzip、brotli 压缩
const zlib = require('zlib');
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const gzip = promisify(zlib.gzip);
const brotliCompress = promisify(zlib.brotliCompress);
// 压缩选项配置
const compressionOptions = {
gzip: {
level: 9, // 最高压缩级别
windowBits: 15,
memLevel: 8
},
brotli: {
params: {
[zlib.constants.BROTLI_PARAM_QUALITY]: 11, // 最高质量
[zlib.constants.BROTLI_PARAM_SIZE_HINT]: 0
}
}
};
// 文件压缩类
class FileCompressor {
constructor(options = {}) {
this.compressionTypes = options.types || ['gzip', 'brotli'];
this.threshold = options.threshold || 1024; // 只压缩大于1KB的文件
}
async compressFile(inputPath, outputDir = null) {
const stats = fs.statSync(inputPath);
if (stats.size < this.threshold) {
console.log(`文件 ${inputPath} 小于阈值,跳过压缩`);
return null;
}
const content = fs.readFileSync(inputPath);
const results = {};
// 并行压缩
const compressionPromises = this.compressionTypes.map(async (type) => {
try {
let compressed;
let extension;
switch (type) {
case 'gzip':
compressed = await gzip(content, compressionOptions.gzip);
extension = '.gz';
break;
case 'brotli':
compressed = await brotliCompress(content, compressionOptions.brotli);
extension = '.br';
break;
default:
throw new Error(`不支持的压缩类型: ${type}`);
}
const outputPath = outputDir
? path.join(outputDir, path.basename(inputPath) + extension)
: inputPath + extension;
fs.writeFileSync(outputPath, compressed);
const compressionRatio = (
(content.length - compressed.length) / content.length * 100
).toFixed(2);
results[type] = {
originalSize: content.length,
compressedSize: compressed.length,
compressionRatio,
outputPath
};
console.log(`${type.toUpperCase()} 压缩完成: ${compressionRatio}% (${outputPath})`);
} catch (error) {
console.error(`${type} 压缩失败:`, error);
}
});
await Promise.all(compressionPromises);
return results;
}
async compressDirectory(inputDir, outputDir, filePattern = /\.(js|css|html|json|svg)$/) {
const files = fs.readdirSync(inputDir);
const targetFiles = files.filter(file => filePattern.test(file));
const results = await Promise.all(
targetFiles.map(file =>
this.compressFile(path.join(inputDir, file), outputDir)
)
);
return results.filter(result => result !== null);
}
// 中间件形式的实时压缩
middleware() {
return (req, res, next) => {
const acceptEncoding = req.headers['accept-encoding'] || '';
// 检查客户端支持的压缩格式
const supportsBrotli = /\bbr\b/.test(acceptEncoding);
const supportsGzip = /\bgzip\b/.test(acceptEncoding);
if (!supportsBrotli && !supportsGzip) {
return next();
}
const originalSend = res.send;
res.send = function(data) {
if (typeof data === 'string' && data.length > 1024) {
const compressionType = supportsBrotli ? 'brotli' : 'gzip';
if (compressionType === 'brotli') {
zlib.brotliCompress(data, compressionOptions.brotli, (err, compressed) => {
if (!err) {
res.set('Content-Encoding', 'br');
res.set('Content-Length', compressed.length);
originalSend.call(this, compressed);
} else {
originalSend.call(this, data);
}
});
} else {
zlib.gzip(data, compressionOptions.gzip, (err, compressed) => {
if (!err) {
res.set('Content-Encoding', 'gzip');
res.set('Content-Length', compressed.length);
originalSend.call(this, compressed);
} else {
originalSend.call(this, data);
}
});
}
} else {
originalSend.call(this, data);
}
};
next();
};
}
}
// 使用示例
const compressor = new FileCompressor({
types: ['gzip', 'brotli'],
threshold: 1024
});
// 压缩构建输出
compressor.compressDirectory('./dist', './dist/compressed');
配置 Nginx 以使用 Gzip 和 Brotli 压缩
// Nginx 配置生成器
class NginxCompressionConfig {
static generateGzipConfig(options = {}) {
const config = {
enabled: options.enabled !== false,
level: options.level || 6,
minLength: options.minLength || 1000,
types: options.types || [
'text/plain',
'text/css',
'text/javascript',
'text/xml',
'application/json',
'application/javascript',
'application/xml+rss',
'application/atom+xml',
'image/svg+xml'
]
};
return `
# Gzip 压缩配置
gzip ${config.enabled ? 'on' : 'off'};
gzip_vary on;
gzip_min_length ${config.minLength};
gzip_proxied any;
gzip_comp_level ${config.level};
gzip_types
${config.types.join('\n ')};
`;
}
static generateBrotliConfig(options = {}) {
const config = {
enabled: options.enabled !== false,
level: options.level || 6,
minLength: options.minLength || 1000,
types: options.types || [
'text/plain',
'text/css',
'text/javascript',
'application/json',
'application/javascript',
'application/xml+rss',
'application/atom+xml',
'image/svg+xml'
]
};
return `
# Brotli 压缩配置 (需要 ngx_brotli 模块)
brotli ${config.enabled ? 'on' : 'off'};
brotli_comp_level ${config.level};
brotli_min_length ${config.minLength};
brotli_types
${config.types.join('\n ')};
`;
}
static generateCompleteConfig(options = {}) {
return `
server {
listen 80;
server_name ${options.serverName || 'example.com'};
root ${options.root || '/var/www/html'};
index index.html index.htm;
${this.generateGzipConfig(options.gzip)}
${this.generateBrotliConfig(options.brotli)}
# 缓存配置
location ~* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
# 预压缩文件支持
location ~ \\.js$ {
gzip_static on;
brotli_static on;
}
location ~ \\.css$ {
gzip_static on;
brotli_static on;
}
}
# HTML 文件缓存
location ~* \\.html$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
}`;
}
}
// 生成 Nginx 配置
const nginxConfig = NginxCompressionConfig.generateCompleteConfig({
serverName: 'myapp.com',
root: '/var/www/myapp',
gzip: {
level: 9,
minLength: 1000
},
brotli: {
level: 11,
minLength: 1000
}
});
console.log(nginxConfig);
请求优化
减少 HTTP 请求数
每次 HTTP 请求都会消耗时间,因此减少HTTP 请求数可以显著提升页面性能。常见的方法包括
-
合并文件:将多个 CSS或 JavaScript 文件合并为一个文件,减少请求次数。
-
内联关键CSS 和 JavaScript:将关键的 CSS 和 Javascript 内联到 HTML 文件中,减少外部请求
文件合并策略实现:
// 资源合并工具
class AssetBundler {
constructor(options = {}) {
this.outputDir = options.outputDir || './dist';
this.bundles = new Map();
}
// 创建 CSS 合并包
createCssBundle(name, files) {
const bundleContent = files.map(file => {
const content = fs.readFileSync(file, 'utf8');
return `/* ${path.basename(file)} */\n${content}\n`;
}).join('\n');
const outputPath = path.join(this.outputDir, `${name}.bundle.css`);
fs.writeFileSync(outputPath, bundleContent);
this.bundles.set(name, {
type: 'css',
files: files.length,
size: Buffer.byteLength(bundleContent, 'utf8'),
path: outputPath
});
return outputPath;
}
// 创建 JavaScript 合并包
createJsBundle(name, files) {
const bundleContent = files.map(file => {
const content = fs.readFileSync(file, 'utf8');
return `/* ${path.basename(file)} */\n(function() {\n${content}\n})();\n`;
}).join('\n');
const outputPath = path.join(this.outputDir, `${name}.bundle.js`);
fs.writeFileSync(outputPath, bundleContent);
this.bundles.set(name, {
type: 'js',
files: files.length,
size: Buffer.byteLength(bundleContent, 'utf8'),
path: outputPath
});
return outputPath;
}
// 生成资源清单
generateManifest() {
const manifest = {};
for (const [name, bundle] of this.bundles) {
manifest[name] = {
type: bundle.type,
files: bundle.files,
size: bundle.size,
url: `/bundles/${path.basename(bundle.path)}`
};
}
const manifestPath = path.join(this.outputDir, 'bundle-manifest.json');
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
return manifest;
}
}
// 智能资源加载器
class ResourceLoader {
constructor() {
this.loadedResources = new Set();
this.loadingPromises = new Map();
}
// 动态加载 CSS
loadCSS(url) {
if (this.loadedResources.has(url)) {
return Promise.resolve();
}
if (this.loadingPromises.has(url)) {
return this.loadingPromises.get(url);
}
const promise = new Promise((resolve, reject) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
link.onload = () => {
this.loadedResources.add(url);
resolve();
};
link.onerror = () => {
this.loadingPromises.delete(url);
reject(new Error(`Failed to load CSS: ${url}`));
};
document.head.appendChild(link);
});
this.loadingPromises.set(url, promise);
return promise;
}
// 动态加载 JavaScript
loadJS(url) {
if (this.loadedResources.has(url)) {
return Promise.resolve();
}
if (this.loadingPromises.has(url)) {
return this.loadingPromises.get(url);
}
const promise = new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.async = true;
script.onload = () => {
this.loadedResources.add(url);
resolve();
};
script.onerror = () => {
this.loadingPromises.delete(url);
reject(new Error(`Failed to load JS: ${url}`));
};
document.head.appendChild(script);
});
this.loadingPromises.set(url, promise);
return promise;
}
// 批量加载资源
async loadBundle(resources) {
const loadPromises = resources.map(resource => {
if (resource.type === 'css') {
return this.loadCSS(resource.url);
} else if (resource.type === 'js') {
return this.loadJS(resource.url);
}
});
try {
await Promise.all(loadPromises);
console.log('资源包加载完成');
} catch (error) {
console.error('资源包加载失败:', error);
throw error;
}
}
}
使用懒加载、预加载、预请求
优化资源加载顺序,可以减少首次渲染时间和整体加载时间。
- 懒加载:对于图片、视频等资源,只有当它们进入视口时才加载,以减少初始页面加载时间。
- 预加载:提前加载即将使用的资源,例如字体、关键 CSS、JavaScript 等。
- 预请求:提前进行 DNS 解析或连接建立,以减少后续请求的延迟。
// 图片懒加载实现
class LazyImageLoader {
constructor(options = {}) {
this.threshold = options.threshold || '50px';
this.observerOptions = {
root: null,
rootMargin: this.threshold,
threshold: 0.1
};
this.observer = new IntersectionObserver(
this.handleIntersection.bind(this),
this.observerOptions
);
this.init();
}
init() {
const lazyImages = document.querySelectorAll('img[data-src]');
lazyImages.forEach(img => {
this.observer.observe(img);
});
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
this.loadImage(img);
this.observer.unobserve(img);
}
});
}
loadImage(img) {
const src = img.getAttribute('data-src');
const srcset = img.getAttribute('data-srcset');
// 创建新的图片对象进行预加载
const imageLoader = new Image();
imageLoader.onload = () => {
// 图片加载完成后再设置 src
img.src = src;
if (srcset) {
img.srcset = srcset;
}
img.classList.add('loaded');
img.removeAttribute('data-src');
img.removeAttribute('data-srcset');
};
imageLoader.onerror = () => {
img.classList.add('error');
};
imageLoader.src = src;
}
// 手动添加懒加载图片
addImage(img) {
this.observer.observe(img);
}
// 销毁观察器
destroy() {
this.observer.disconnect();
}
}
// 智能预加载管理器
class IntelligentPreloader {
constructor() {
this.preloadCache = new Map();
this.priorityQueue = [];
this.isPreloading = false;
}
// 根据用户行为预测并预加载资源
predictAndPreload(currentPage) {
const predictions = this.getPredictions(currentPage);
predictions.forEach(prediction => {
this.addToQueue(prediction.resource, prediction.priority);
});
this.processQueue();
}
getPredictions(currentPage) {
// 基于历史数据和用户行为的预测逻辑
const commonRoutes = {
'/': ['/products', '/about'],
'/products': ['/product-detail', '/cart'],
'/product-detail': ['/cart', '/checkout']
};
const nextPages = commonRoutes[currentPage] || [];
return nextPages.map(page => ({
resource: page,
priority: this.calculatePriority(page, currentPage)
}));
}
calculatePriority(targetPage, currentPage) {
// 优先级计算算法
const baseScore = 100;
const userBehaviorData = this.getUserBehaviorData();
// 基于用户历史行为调整优先级
const transitionProbability = userBehaviorData[`${currentPage}->${targetPage}`] || 0.1;
return Math.floor(baseScore * transitionProbability);
}
addToQueue(resource, priority) {
this.priorityQueue.push({ resource, priority });
this.priorityQueue.sort((a, b) => b.priority - a.priority);
}
async processQueue() {
if (this.isPreloading || this.priorityQueue.length === 0) {
return;
}
this.isPreloading = true;
while (this.priorityQueue.length > 0) {
const { resource, priority } = this.priorityQueue.shift();
if (!this.preloadCache.has(resource)) {
await this.preloadResource(resource);
}
// 控制预加载频率,避免影响当前页面性能
await this.delay(100);
}
this.isPreloading = false;
}
async preloadResource(resource) {
try {
// 使用 fetch 进行预加载
const response = await fetch(resource, {
method: 'GET',
cache: 'force-cache'
});
if (response.ok) {
this.preloadCache.set(resource, {
data: await response.text(),
timestamp: Date.now()
});
console.log(`预加载完成: ${resource}`);
}
} catch (error) {
console.warn(`预加载失败: ${resource}`, error);
}
}
// 使用 link 标签进行资源预加载
preloadWithLinkTag(href, as = 'fetch', crossorigin = 'anonymous') {
const link = document.createElement('link');
link.rel = 'preload';
link.href = href;
link.as = as;
if (crossorigin) {
link.crossOrigin = crossorigin;
}
document.head.appendChild(link);
return new Promise((resolve, reject) => {
link.onload = resolve;
link.onerror = reject;
});
}
// 实现 prefetch
prefetchResource(href) {
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = href;
document.head.appendChild(link);
}
getUserBehaviorData() {
// 从 localStorage 或服务器获取用户行为数据
const data = localStorage.getItem('userBehaviorData');
return data ? JSON.parse(data) : {};
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 组件级别的懒加载
class ComponentLazyLoader {
constructor() {
this.componentCache = new Map();
this.importMap = new Map();
}
// 注册组件的动态导入函数
registerComponent(name, importFunction) {
this.importMap.set(name, importFunction);
}
// 懒加载组件
async loadComponent(name) {
if (this.componentCache.has(name)) {
return this.componentCache.get(name);
}
const importFunction = this.importMap.get(name);
if (!importFunction) {
throw new Error(`Component ${name} not registered`);
}
try {
const module = await importFunction();
const component = module.default || module;
this.componentCache.set(name, component);
return component;
} catch (error) {
console.error(`Failed to load component ${name}:`, error);
throw error;
}
}
// 预加载高优先级组件
async preloadCriticalComponents(componentNames) {
const loadPromises = componentNames.map(name =>
this.loadComponent(name).catch(error => {
console.warn(`Failed to preload component ${name}:`, error);
})
);
await Promise.allSettled(loadPromises);
}
}
// 使用示例
const lazyLoader = new LazyImageLoader({ threshold: '100px' });
const preloader = new IntelligentPreloader();
const componentLoader = new ComponentLazyLoader();
// 注册组件
componentLoader.registerComponent('ProductList', () => import('./components/ProductList.js'));
componentLoader.registerComponent('UserProfile', () => import('./components/UserProfile.js'));
// 预加载关键组件
componentLoader.preloadCriticalComponents(['ProductList']);
使用 HTTP/2 多路复用
HTTP/2 是一种可以显著减少延迟、提高网页加载速度的协议。它支持多路复用(Multiplexing),允许多个请求和响应在一个连接中同时进行。
// HTTP/2 连接管理器
class HTTP2ConnectionManager {
constructor(options = {}) {
this.maxConcurrentStreams = options.maxConcurrentStreams || 100;
this.connections = new Map();
this.streamQueue = [];
this.activeStreams = 0;
}
// 智能请求合并
async batchRequests(requests) {
const groupedRequests = this.groupRequestsByDomain(requests);
const results = {};
for (const [domain, domainRequests] of groupedRequests) {
results[domain] = await this.processDomainRequests(domain, domainRequests);
}
return results;
}
groupRequestsByDomain(requests) {
const grouped = new Map();
requests.forEach(request => {
const url = new URL(request.url);
const domain = url.origin;
if (!grouped.has(domain)) {
grouped.set(domain, []);
}
grouped.get(domain).push(request);
});
return grouped;
}
async processDomainRequests(domain, requests) {
// HTTP/2 连接复用
const connection = await this.getConnection(domain);
const chunks = this.chunkRequests(requests, this.maxConcurrentStreams);
const results = [];
for (const chunk of chunks) {
const chunkResults = await Promise.all(
chunk.map(request => this.makeRequest(request, connection))
);
results.push(...chunkResults);
}
return results;
}
chunkRequests(requests, chunkSize) {
const chunks = [];
for (let i = 0; i < requests.length; i += chunkSize) {
chunks.push(requests.slice(i, i + chunkSize));
}
return chunks;
}
async getConnection(domain) {
if (this.connections.has(domain)) {
return this.connections.get(domain);
}
// 检查 HTTP/2 支持
const connection = {
domain,
supportsHTTP2: await this.checkHTTP2Support(domain),
activeStreams: 0,
lastUsed: Date.now()
};
this.connections.set(domain, connection);
return connection;
}
async checkHTTP2Support(domain) {
try {
// 使用 fetch 检查 HTTP/2 支持
const response = await fetch(`${domain}/`, {
method: 'HEAD',
cache: 'no-cache'
});
// 检查响应头中的协议版本
return response.headers.get(':status') !== null;
} catch (error) {
console.warn(`无法检查 ${domain} 的 HTTP/2 支持:`, error);
return false;
}
}
async makeRequest(request, connection) {
try {
connection.activeStreams++;
connection.lastUsed = Date.now();
const response = await fetch(request.url, {
method: request.method || 'GET',
headers: request.headers || {},
body: request.body,
...request.options
});
connection.activeStreams--;
return {
url: request.url,
status: response.status,
headers: Object.fromEntries(response.headers.entries()),
data: await response.json().catch(() => response.text())
};
} catch (error) {
connection.activeStreams--;
throw new Error(`请求失败 ${request.url}: ${error.message}`);
}
}
// 服务器推送资源预加载
handleServerPush(pushStreams) {
pushStreams.forEach(stream => {
const { url, priority } = stream;
// 缓存推送的资源
this.cacheResource(url, stream.data, {
priority,
source: 'server-push',
timestamp: Date.now()
});
});
}
cacheResource(url, data, metadata) {
const cache = this.getResourceCache();
cache.set(url, {
data,
metadata,
expiresAt: Date.now() + (metadata.maxAge || 3600000) // 默认1小时
});
}
getResourceCache() {
if (!this.resourceCache) {
this.resourceCache = new Map();
}
return this.resourceCache;
}
// 连接清理
cleanupConnections() {
const now = Date.now();
const timeout = 300000; // 5分钟
for (const [domain, connection] of this.connections) {
if (now - connection.lastUsed > timeout && connection.activeStreams === 0) {
this.connections.delete(domain);
console.log(`清理闲置连接: ${domain}`);
}
}
}
}
// HTTP/2 推送管理器
class ServerPushManager {
constructor() {
this.pushCache = new Map();
this.criticalResources = new Set();
}
// 标记关键资源
markCritical(resources) {
resources.forEach(resource => {
this.criticalResources.add(resource);
});
}
// 生成推送清单
generatePushManifest(entryPoint) {
const manifest = {
[entryPoint]: []
};
// 分析依赖关系
const dependencies = this.analyzeDependencies(entryPoint);
dependencies.forEach(dep => {
if (this.criticalResources.has(dep.url)) {
manifest[entryPoint].push({
url: dep.url,
type: dep.type,
priority: dep.priority || 'high'
});
}
});
return manifest;
}
analyzeDependencies(entryPoint) {
// 依赖分析逻辑(可以集成 Webpack 分析结果)
return [
{ url: '/css/critical.css', type: 'style', priority: 'high' },
{ url: '/js/vendor.js', type: 'script', priority: 'high' },
{ url: '/js/app.js', type: 'script', priority: 'medium' }
];
}
}
// 使用示例
const http2Manager = new HTTP2ConnectionManager({
maxConcurrentStreams: 50
});
const pushManager = new ServerPushManager();
pushManager.markCritical(['/css/critical.css', '/js/vendor.js']);
// 批量请求示例
const requests = [
{ url: '/api/user', method: 'GET' },
{ url: '/api/products', method: 'GET' },
{ url: '/api/cart', method: 'GET' }
];
http2Manager.batchRequests(requests).then(results => {
console.log('批量请求完成:', results);
});
优化服务器响应时间
优化服务器的响应时间可以减少浏览器等待时间,从而加快页面加载速度。常见的方法包括:
-
启用服务器缓存:使用HTTP 缓存头,如 Cache-Control、ETag 等,減少不必要的服务器请求。
-
使用CDN:利用内容分发网络(CDN)将静态资源分发到全球多个服务器节点,减少用户请求的延迟。
-
优化数据库查询:缓存频繁访问的数据,减少数据库查询时间。
// 服务器性能监控器
class ServerPerformanceMonitor {
constructor(options = {}) {
this.thresholds = {
responseTime: options.responseTimeThreshold || 200, // ms
errorRate: options.errorRateThreshold || 0.01, // 1%
throughput: options.throughputThreshold || 1000 // requests/min
};
this.metrics = {
responseTimes: [],
errors: 0,
requests: 0,
startTime: Date.now()
};
this.alerts = [];
}
// 记录请求指标
recordRequest(responseTime, isError = false) {
this.metrics.requests++;
this.metrics.responseTimes.push({
time: responseTime,
timestamp: Date.now()
});
if (isError) {
this.metrics.errors++;
}
// 清理旧数据 (保留最近1小时)
this.cleanOldMetrics();
this.checkThresholds();
}
cleanOldMetrics() {
const oneHourAgo = Date.now() - 3600000;
this.metrics.responseTimes = this.metrics.responseTimes.filter(
metric => metric.timestamp > oneHourAgo
);
}
checkThresholds() {
const avgResponseTime = this.getAverageResponseTime();
const errorRate = this.getErrorRate();
const throughput = this.getThroughput();
if (avgResponseTime > this.thresholds.responseTime) {
this.triggerAlert('HIGH_RESPONSE_TIME', {
current: avgResponseTime,
threshold: this.thresholds.responseTime
});
}
if (errorRate > this.thresholds.errorRate) {
this.triggerAlert('HIGH_ERROR_RATE', {
current: errorRate,
threshold: this.thresholds.errorRate
});
}
if (throughput < this.thresholds.throughput) {
this.triggerAlert('LOW_THROUGHPUT', {
current: throughput,
threshold: this.thresholds.throughput
});
}
}
getAverageResponseTime() {
if (this.metrics.responseTimes.length === 0) return 0;
const sum = this.metrics.responseTimes.reduce((acc, metric) => acc + metric.time, 0);
return sum / this.metrics.responseTimes.length;
}
getErrorRate() {
return this.metrics.requests > 0 ? this.metrics.errors / this.metrics.requests : 0;
}
getThroughput() {
const durationMinutes = (Date.now() - this.metrics.startTime) / 60000;
return durationMinutes > 0 ? this.metrics.requests / durationMinutes : 0;
}
triggerAlert(type, data) {
const alert = {
type,
data,
timestamp: Date.now()
};
this.alerts.push(alert);
console.warn(`性能告警 [${type}]:`, data);
// 发送告警通知
this.sendAlert(alert);
}
async sendAlert(alert) {
try {
await fetch('/api/alerts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(alert)
});
} catch (error) {
console.error('发送告警失败:', error);
}
}
// 生成性能报告
generateReport() {
return {
summary: {
totalRequests: this.metrics.requests,
errorCount: this.metrics.errors,
averageResponseTime: this.getAverageResponseTime(),
errorRate: this.getErrorRate(),
throughput: this.getThroughput()
},
thresholds: this.thresholds,
alerts: this.alerts.slice(-10), // 最近10个告警
recommendations: this.getRecommendations()
};
}
getRecommendations() {
const recommendations = [];
const avgResponseTime = this.getAverageResponseTime();
const errorRate = this.getErrorRate();
if (avgResponseTime > 500) {
recommendations.push({
type: 'PERFORMANCE',
message: '响应时间过长,建议优化数据库查询或启用缓存'
});
}
if (errorRate > 0.05) {
recommendations.push({
type: 'RELIABILITY',
message: '错误率过高,建议检查错误日志并修复问题'
});
}
return recommendations;
}
}
// 响应时间优化工具
class ResponseTimeOptimizer {
constructor() {
this.cache = new Map();
this.queryOptimizer = new DatabaseQueryOptimizer();
}
// 缓存装饰器
cache(ttl = 300000) { // 默认5分钟
return (target, propertyName, descriptor) => {
const method = descriptor.value;
descriptor.value = async function(...args) {
const cacheKey = `${propertyName}:${JSON.stringify(args)}`;
if (this.cache.has(cacheKey)) {
const cached = this.cache.get(cacheKey);
if (Date.now() - cached.timestamp < ttl) {
return cached.data;
}
}
const result = await method.apply(this, args);
this.cache.set(cacheKey, {
data: result,
timestamp: Date.now()
});
return result;
};
return descriptor;
};
}
// 数据库查询优化
async optimizeQuery(query, params) {
const optimizedQuery = this.queryOptimizer.optimize(query);
const startTime = Date.now();
try {
const result = await this.executeQuery(optimizedQuery, params);
const responseTime = Date.now() - startTime;
// 记录查询性能
this.recordQueryPerformance(query, responseTime);
return result;
} catch (error) {
console.error('查询执行失败:', error);
throw error;
}
}
recordQueryPerformance(query, responseTime) {
// 记录查询性能数据,用于后续优化分析
console.log(`查询性能: ${query.substring(0, 50)}... - ${responseTime}ms`);
}
// 并行处理优化
async parallelProcess(tasks) {
const startTime = Date.now();
try {
const results = await Promise.all(tasks.map(async (task, index) => {
const taskStartTime = Date.now();
const result = await task();
const taskDuration = Date.now() - taskStartTime;
return {
index,
result,
duration: taskDuration
};
}));
const totalDuration = Date.now() - startTime;
console.log(`并行处理完成: ${tasks.length} 个任务,总耗时 ${totalDuration}ms`);
return results.sort((a, b) => a.index - b.index).map(r => r.result);
} catch (error) {
console.error('并行处理失败:', error);
throw error;
}
}
}
// 数据库查询优化器
class DatabaseQueryOptimizer {
optimize(query) {
let optimizedQuery = query;
// 添加适当的索引提示
optimizedQuery = this.addIndexHints(optimizedQuery);
// 优化 JOIN 顺序
optimizedQuery = this.optimizeJoins(optimizedQuery);
// 添加查询限制
optimizedQuery = this.addQueryLimits(optimizedQuery);
return optimizedQuery;
}
addIndexHints(query) {
// 简单的索引提示添加逻辑
return query;
}
optimizeJoins(query) {
// JOIN 优化逻辑
return query;
}
addQueryLimits(query) {
// 添加合理的 LIMIT
if (!query.toLowerCase().includes('limit') &&
query.toLowerCase().includes('select')) {
return query + ' LIMIT 1000';
}
return query;
}
}
// 使用示例
const monitor = new ServerPerformanceMonitor({
responseTimeThreshold: 150,
errorRateThreshold: 0.02
});
const optimizer = new ResponseTimeOptimizer();
// Express 中间件示例
function performanceMiddleware(req, res, next) {
const startTime = Date.now();
res.on('finish', () => {
const responseTime = Date.now() - startTime;
const isError = res.statusCode >= 400;
monitor.recordRequest(responseTime, isError);
});
next();
}