本章概述
性能优化是现代 Web 开发中的关键环节,特别是在使用 Bootstrap 这样的 CSS 框架时。本章将深入探讨如何优化 Bootstrap 项目的性能,包括 CSS 优化、JavaScript 优化、资源加载优化、构建流程优化等方面,并提供实用的最佳实践指南。
学习目标
- 掌握 Bootstrap 项目的性能分析方法
- 学习 CSS 和 JavaScript 的优化技术
- 了解资源加载和缓存策略
- 掌握构建工具的性能优化配置
- 学习移动端性能优化技巧
- 了解可访问性和 SEO 优化
CSS 性能优化
1. 减少 CSS 文件大小
移除未使用的 CSS
// postcss.config.js
// 使用 PurgeCSS 移除未使用的样式
const purgecss = require('@fullhuman/postcss-purgecss');
module.exports = {
plugins: [
purgecss({
content: [
'./src/**/*.html',
'./src/**/*.js',
'./src/**/*.jsx',
'./src/**/*.ts',
'./src/**/*.tsx',
'./src/**/*.vue'
],
// 保护 Bootstrap 的动态类名
safelist: [
/^btn-/,
/^alert-/,
/^badge-/,
/^bg-/,
/^text-/,
/^border-/,
/^rounded-/,
/^shadow-/,
/^p[xy]?-/,
/^m[xy]?-/,
/^w-/,
/^h-/,
/^d-/,
/^flex-/,
/^justify-/,
/^align-/,
/^position-/,
/^top-/,
/^bottom-/,
/^start-/,
/^end-/,
/^translate-/,
/^rotate-/,
/^scale-/,
/^opacity-/,
/^overflow-/,
/^user-/,
/^pointer-/,
/^cursor-/,
/^z-/,
/^col-/,
/^row-/,
/^g-/,
/^gx-/,
/^gy-/,
/^offset-/,
/^order-/,
// 动态生成的类名
/^carousel-/,
/^modal-/,
/^dropdown-/,
/^nav-/,
/^navbar-/,
/^accordion-/,
/^toast-/,
/^tooltip-/,
/^popover-/,
/^offcanvas-/,
// 状态类
'show',
'hide',
'active',
'disabled',
'fade',
'collapse',
'collapsing',
'was-validated',
'is-valid',
'is-invalid'
],
// 默认提取器
defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || [],
// 自定义提取器
extractors: [
{
extractor: content => {
// 提取 JavaScript 中的类名
const jsClasses = content.match(/["'`]([\w\s-]+)["'`]/g) || [];
return jsClasses.map(match => match.slice(1, -1).split(' ')).flat();
},
extensions: ['js', 'jsx', 'ts', 'tsx']
},
{
extractor: content => {
// 提取 Vue 模板中的类名
const vueClasses = content.match(/:?class="([^"]*)"/g) || [];
return vueClasses.map(match => {
const classStr = match.match(/"([^"]*)"/)[1];
return classStr.split(' ');
}).flat();
},
extensions: ['vue']
}
]
})
]
};
自定义 Bootstrap 构建
// custom-bootstrap.scss
// 只导入需要的 Bootstrap 组件
// 1. 导入必需的文件
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins";
// 2. 自定义变量(可选)
$primary: #007bff;
$secondary: #6c757d;
$enable-rounded: true;
$enable-shadows: false;
$enable-gradients: false;
$enable-transitions: true;
$enable-reduced-motion: true;
$enable-grid-classes: true;
$enable-container-classes: true;
// 3. 导入布局和可重用组件
@import "~bootstrap/scss/root";
@import "~bootstrap/scss/reboot";
@import "~bootstrap/scss/type";
@import "~bootstrap/scss/images";
@import "~bootstrap/scss/containers";
@import "~bootstrap/scss/grid";
// 4. 导入需要的组件(按需选择)
@import "~bootstrap/scss/tables";
@import "~bootstrap/scss/forms";
@import "~bootstrap/scss/buttons";
@import "~bootstrap/scss/transitions";
@import "~bootstrap/scss/dropdown";
@import "~bootstrap/scss/button-group";
@import "~bootstrap/scss/nav";
@import "~bootstrap/scss/navbar";
@import "~bootstrap/scss/card";
@import "~bootstrap/scss/accordion";
@import "~bootstrap/scss/breadcrumb";
@import "~bootstrap/scss/pagination";
@import "~bootstrap/scss/badge";
@import "~bootstrap/scss/alert";
@import "~bootstrap/scss/progress";
@import "~bootstrap/scss/list-group";
@import "~bootstrap/scss/close";
@import "~bootstrap/scss/toasts";
@import "~bootstrap/scss/modal";
@import "~bootstrap/scss/tooltip";
@import "~bootstrap/scss/popover";
@import "~bootstrap/scss/carousel";
@import "~bootstrap/scss/spinners";
@import "~bootstrap/scss/offcanvas";
@import "~bootstrap/scss/placeholders";
// 5. 导入工具类(可选择性导入)
@import "~bootstrap/scss/utilities";
// 6. 导入打印样式(可选)
// @import "~bootstrap/scss/print";
2. CSS 压缩和优化
// webpack.config.js
// Webpack CSS 优化配置
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
mode: 'production',
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 2,
sourceMap: false
}
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
['autoprefixer'],
['cssnano', {
preset: ['default', {
discardComments: {
removeAll: true
},
normalizeWhitespace: true,
colormin: true,
convertValues: true,
discardDuplicates: true,
discardEmpty: true,
mergeIdents: true,
mergeLonghand: true,
mergeRules: true,
minifyFontValues: true,
minifyGradients: true,
minifyParams: true,
minifySelectors: true,
normalizeCharset: true,
normalizeDisplayValues: true,
normalizePositions: true,
normalizeRepeatStyle: true,
normalizeString: true,
normalizeTimingFunctions: true,
normalizeUnicode: true,
normalizeUrl: true,
orderedValues: true,
reduceIdents: true,
reduceInitial: true,
reduceTransforms: true,
svgo: true,
uniqueSelectors: true
}]
}]
]
}
}
},
{
loader: 'sass-loader',
options: {
implementation: require('sass'),
sourceMap: false,
sassOptions: {
outputStyle: 'compressed'
}
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].chunk.css'
})
],
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
}),
new CssMinimizerPlugin({
minimizerOptions: {
preset: [
'default',
{
discardComments: { removeAll: true }
}
]
}
})
],
splitChunks: {
cacheGroups: {
styles: {
name: 'styles',
type: 'css/mini-extract',
chunks: 'all',
enforce: true
}
}
}
}
};
3. 关键 CSS 内联
// critical-css.js
// 提取和内联关键 CSS
const critical = require('critical');
const path = require('path');
// 生成关键 CSS
critical.generate({
// 基础配置
base: 'dist/',
src: 'index.html',
dest: 'index-critical.html',
// 视口尺寸
dimensions: [
{
width: 320,
height: 480
},
{
width: 768,
height: 1024
},
{
width: 1200,
height: 900
}
],
// 内联选项
inline: true,
minify: true,
extract: true,
// 忽略的规则
ignore: {
atrule: ['@font-face'],
rule: [/\.sr-only/],
decl: (node, value) => {
// 忽略某些属性
return /url\(/.test(value);
}
},
// 包含的选择器
include: [
// Bootstrap 核心样式
/\.container/,
/\.row/,
/\.col/,
/\.btn/,
/\.nav/,
/\.navbar/,
/\.card/,
/\.alert/,
// 自定义关键样式
/\.hero/,
/\.header/,
/\.main-content/
],
// 用户代理
userAgent: 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'
}, (err, output) => {
if (err) {
console.error('Critical CSS generation failed:', err);
} else {
console.log('Critical CSS generated successfully');
console.log('Critical CSS size:', output.css.length, 'bytes');
}
});
JavaScript 性能优化
1. 按需加载 Bootstrap JavaScript
// bootstrap-loader.js
// 动态加载 Bootstrap 组件
class BootstrapLoader {
constructor() {
this.loadedComponents = new Set();
this.loadingPromises = new Map();
}
// 动态加载组件
async loadComponent(componentName) {
if (this.loadedComponents.has(componentName)) {
return Promise.resolve();
}
if (this.loadingPromises.has(componentName)) {
return this.loadingPromises.get(componentName);
}
const loadPromise = this._loadComponentModule(componentName);
this.loadingPromises.set(componentName, loadPromise);
try {
await loadPromise;
this.loadedComponents.add(componentName);
this.loadingPromises.delete(componentName);
} catch (error) {
this.loadingPromises.delete(componentName);
throw error;
}
return loadPromise;
}
// 加载组件模块
async _loadComponentModule(componentName) {
switch (componentName) {
case 'modal':
const { Modal } = await import('bootstrap/js/dist/modal');
window.bootstrap = window.bootstrap || {};
window.bootstrap.Modal = Modal;
break;
case 'dropdown':
const { Dropdown } = await import('bootstrap/js/dist/dropdown');
window.bootstrap = window.bootstrap || {};
window.bootstrap.Dropdown = Dropdown;
break;
case 'carousel':
const { Carousel } = await import('bootstrap/js/dist/carousel');
window.bootstrap = window.bootstrap || {};
window.bootstrap.Carousel = Carousel;
break;
case 'collapse':
const { Collapse } = await import('bootstrap/js/dist/collapse');
window.bootstrap = window.bootstrap || {};
window.bootstrap.Collapse = Collapse;
break;
case 'tooltip':
const { Tooltip } = await import('bootstrap/js/dist/tooltip');
window.bootstrap = window.bootstrap || {};
window.bootstrap.Tooltip = Tooltip;
break;
case 'popover':
const { Popover } = await import('bootstrap/js/dist/popover');
window.bootstrap = window.bootstrap || {};
window.bootstrap.Popover = Popover;
break;
case 'toast':
const { Toast } = await import('bootstrap/js/dist/toast');
window.bootstrap = window.bootstrap || {};
window.bootstrap.Toast = Toast;
break;
case 'alert':
const { Alert } = await import('bootstrap/js/dist/alert');
window.bootstrap = window.bootstrap || {};
window.bootstrap.Alert = Alert;
break;
case 'button':
const { Button } = await import('bootstrap/js/dist/button');
window.bootstrap = window.bootstrap || {};
window.bootstrap.Button = Button;
break;
case 'offcanvas':
const { Offcanvas } = await import('bootstrap/js/dist/offcanvas');
window.bootstrap = window.bootstrap || {};
window.bootstrap.Offcanvas = Offcanvas;
break;
case 'scrollspy':
const { ScrollSpy } = await import('bootstrap/js/dist/scrollspy');
window.bootstrap = window.bootstrap || {};
window.bootstrap.ScrollSpy = ScrollSpy;
break;
case 'tab':
const { Tab } = await import('bootstrap/js/dist/tab');
window.bootstrap = window.bootstrap || {};
window.bootstrap.Tab = Tab;
break;
default:
throw new Error(`Unknown Bootstrap component: ${componentName}`);
}
}
// 批量加载组件
async loadComponents(componentNames) {
const loadPromises = componentNames.map(name => this.loadComponent(name));
return Promise.all(loadPromises);
}
// 预加载组件
preloadComponent(componentName) {
// 使用 requestIdleCallback 在空闲时预加载
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
this.loadComponent(componentName).catch(console.error);
});
} else {
setTimeout(() => {
this.loadComponent(componentName).catch(console.error);
}, 100);
}
}
// 自动检测并加载页面中的组件
autoLoadComponents() {
const componentSelectors = {
modal: '[data-bs-toggle="modal"]',
dropdown: '[data-bs-toggle="dropdown"]',
carousel: '.carousel',
collapse: '[data-bs-toggle="collapse"]',
tooltip: '[data-bs-toggle="tooltip"]',
popover: '[data-bs-toggle="popover"]',
toast: '.toast',
alert: '.alert-dismissible',
offcanvas: '[data-bs-toggle="offcanvas"]',
scrollspy: '[data-bs-spy="scroll"]',
tab: '[data-bs-toggle="tab"], [data-bs-toggle="pill"]'
};
const componentsToLoad = [];
Object.entries(componentSelectors).forEach(([component, selector]) => {
if (document.querySelector(selector)) {
componentsToLoad.push(component);
}
});
if (componentsToLoad.length > 0) {
return this.loadComponents(componentsToLoad);
}
return Promise.resolve();
}
}
// 创建全局实例
const bootstrapLoader = new BootstrapLoader();
// 导出
export default bootstrapLoader;
// 使用示例
// import bootstrapLoader from './bootstrap-loader';
//
// // 自动加载页面中的组件
// document.addEventListener('DOMContentLoaded', () => {
// bootstrapLoader.autoLoadComponents();
// });
//
// // 手动加载特定组件
// bootstrapLoader.loadComponent('modal').then(() => {
// const modal = new bootstrap.Modal(document.getElementById('myModal'));
// modal.show();
// });
2. JavaScript 代码分割和懒加载
// lazy-components.js
// 懒加载组件管理器
class LazyComponentManager {
constructor() {
this.observers = new Map();
this.loadedComponents = new Set();
this.componentRegistry = new Map();
// 注册组件加载器
this.registerComponents();
}
// 注册组件
registerComponents() {
// 模态框组件
this.componentRegistry.set('modal', {
selector: '[data-lazy-modal]',
loader: () => import('./components/modal-component'),
dependencies: ['modal']
});
// 轮播图组件
this.componentRegistry.set('carousel', {
selector: '[data-lazy-carousel]',
loader: () => import('./components/carousel-component'),
dependencies: ['carousel']
});
// 图表组件
this.componentRegistry.set('chart', {
selector: '[data-lazy-chart]',
loader: () => import('./components/chart-component'),
dependencies: []
});
// 数据表格组件
this.componentRegistry.set('datatable', {
selector: '[data-lazy-datatable]',
loader: () => import('./components/datatable-component'),
dependencies: []
});
// 地图组件
this.componentRegistry.set('map', {
selector: '[data-lazy-map]',
loader: () => import('./components/map-component'),
dependencies: []
});
}
// 初始化懒加载
init() {
if (!('IntersectionObserver' in window)) {
// 降级处理:直接加载所有组件
this.loadAllComponents();
return;
}
this.componentRegistry.forEach((config, componentName) => {
this.observeComponent(componentName, config);
});
}
// 观察组件
observeComponent(componentName, config) {
const elements = document.querySelectorAll(config.selector);
if (elements.length === 0) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadComponent(componentName, entry.target);
observer.unobserve(entry.target);
}
});
},
{
rootMargin: '50px 0px',
threshold: 0.1
}
);
elements.forEach(element => {
observer.observe(element);
});
this.observers.set(componentName, observer);
}
// 加载组件
async loadComponent(componentName, element) {
if (this.loadedComponents.has(componentName)) {
return;
}
const config = this.componentRegistry.get(componentName);
if (!config) {
console.warn(`Component ${componentName} not found`);
return;
}
try {
// 显示加载状态
this.showLoadingState(element);
// 加载 Bootstrap 依赖
if (config.dependencies.length > 0) {
await bootstrapLoader.loadComponents(config.dependencies);
}
// 加载组件模块
const module = await config.loader();
const ComponentClass = module.default || module[componentName];
// 初始化组件
if (ComponentClass) {
new ComponentClass(element);
}
this.loadedComponents.add(componentName);
this.hideLoadingState(element);
// 触发加载完成事件
element.dispatchEvent(new CustomEvent('component:loaded', {
detail: { componentName }
}));
} catch (error) {
console.error(`Failed to load component ${componentName}:`, error);
this.showErrorState(element, error);
}
}
// 显示加载状态
showLoadingState(element) {
const loader = document.createElement('div');
loader.className = 'component-loader d-flex justify-content-center align-items-center';
loader.innerHTML = `
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
`;
loader.style.cssText = `
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
z-index: 1000;
`;
element.style.position = 'relative';
element.appendChild(loader);
}
// 隐藏加载状态
hideLoadingState(element) {
const loader = element.querySelector('.component-loader');
if (loader) {
loader.remove();
}
}
// 显示错误状态
showErrorState(element, error) {
const errorDiv = document.createElement('div');
errorDiv.className = 'alert alert-danger';
errorDiv.innerHTML = `
<h6>组件加载失败</h6>
<p class="mb-0">请刷新页面重试</p>
`;
this.hideLoadingState(element);
element.appendChild(errorDiv);
}
// 加载所有组件(降级处理)
async loadAllComponents() {
const loadPromises = [];
this.componentRegistry.forEach((config, componentName) => {
const elements = document.querySelectorAll(config.selector);
elements.forEach(element => {
loadPromises.push(this.loadComponent(componentName, element));
});
});
await Promise.allSettled(loadPromises);
}
// 预加载组件
preloadComponent(componentName) {
const config = this.componentRegistry.get(componentName);
if (!config) return;
// 使用 link preload 预加载模块
const link = document.createElement('link');
link.rel = 'modulepreload';
link.href = config.loader.toString().match(/import\('([^']+)'\)/)?.[1];
if (link.href) {
document.head.appendChild(link);
}
}
// 销毁观察器
destroy() {
this.observers.forEach(observer => {
observer.disconnect();
});
this.observers.clear();
}
}
// 创建全局实例
const lazyComponentManager = new LazyComponentManager();
// 导出
export default lazyComponentManager;
3. 事件委托优化
// event-delegation.js
// 高性能事件委托管理器
class EventDelegationManager {
constructor() {
this.delegatedEvents = new Map();
this.eventHandlers = new Map();
this.throttledEvents = new Set(['scroll', 'resize', 'mousemove']);
this.debouncedEvents = new Set(['input', 'keyup']);
this.init();
}
init() {
// 设置全局事件委托
this.setupGlobalDelegation();
// 优化滚动和调整大小事件
this.optimizePerformanceEvents();
}
// 设置全局事件委托
setupGlobalDelegation() {
// Bootstrap 组件事件委托
this.delegate('click', '[data-bs-toggle]', this.handleBootstrapToggle.bind(this));
this.delegate('click', '[data-bs-dismiss]', this.handleBootstrapDismiss.bind(this));
this.delegate('click', '.btn', this.handleButtonClick.bind(this));
this.delegate('submit', 'form', this.handleFormSubmit.bind(this));
// 自定义组件事件委托
this.delegate('click', '[data-action]', this.handleDataAction.bind(this));
this.delegate('change', '[data-filter]', this.handleDataFilter.bind(this));
this.delegate('input', '[data-search]', this.handleDataSearch.bind(this));
}
// 事件委托
delegate(eventType, selector, handler, options = {}) {
const eventKey = `${eventType}:${selector}`;
if (this.delegatedEvents.has(eventKey)) {
return; // 避免重复绑定
}
let wrappedHandler = handler;
// 应用节流或防抖
if (this.throttledEvents.has(eventType)) {
wrappedHandler = this.throttle(handler, options.throttleDelay || 16);
} else if (this.debouncedEvents.has(eventType)) {
wrappedHandler = this.debounce(handler, options.debounceDelay || 300);
}
const delegatedHandler = (event) => {
const target = event.target.closest(selector);
if (target) {
wrappedHandler.call(target, event, target);
}
};
document.addEventListener(eventType, delegatedHandler, {
passive: options.passive !== false,
capture: options.capture || false
});
this.delegatedEvents.set(eventKey, delegatedHandler);
this.eventHandlers.set(eventKey, handler);
}
// 处理 Bootstrap toggle 事件
handleBootstrapToggle(event, element) {
const toggle = element.getAttribute('data-bs-toggle');
const target = element.getAttribute('data-bs-target') || element.getAttribute('href');
// 确保组件已加载
bootstrapLoader.loadComponent(toggle).then(() => {
// 让 Bootstrap 处理默认行为
}).catch(console.error);
}
// 处理 Bootstrap dismiss 事件
handleBootstrapDismiss(event, element) {
const dismiss = element.getAttribute('data-bs-dismiss');
// 确保组件已加载
bootstrapLoader.loadComponent(dismiss).then(() => {
// 让 Bootstrap 处理默认行为
}).catch(console.error);
}
// 处理按钮点击
handleButtonClick(event, element) {
// 防止重复点击
if (element.disabled || element.classList.contains('disabled')) {
event.preventDefault();
return;
}
// 添加点击反馈
this.addClickFeedback(element);
}
// 处理表单提交
handleFormSubmit(event, element) {
// 表单验证
if (!element.checkValidity()) {
event.preventDefault();
element.classList.add('was-validated');
return;
}
// 防止重复提交
const submitBtn = element.querySelector('[type="submit"]');
if (submitBtn) {
submitBtn.disabled = true;
setTimeout(() => {
submitBtn.disabled = false;
}, 2000);
}
}
// 处理数据操作
handleDataAction(event, element) {
const action = element.getAttribute('data-action');
const target = element.getAttribute('data-target');
switch (action) {
case 'toggle-class':
const className = element.getAttribute('data-class');
const targetElement = document.querySelector(target);
if (targetElement && className) {
targetElement.classList.toggle(className);
}
break;
case 'copy-text':
const text = element.getAttribute('data-text') || element.textContent;
this.copyToClipboard(text);
break;
case 'scroll-to':
const scrollTarget = document.querySelector(target);
if (scrollTarget) {
scrollTarget.scrollIntoView({ behavior: 'smooth' });
}
break;
}
}
// 处理数据过滤
handleDataFilter(event, element) {
const filterValue = element.value;
const filterTarget = element.getAttribute('data-filter');
const items = document.querySelectorAll(`[data-filter-item="${filterTarget}"]`);
items.forEach(item => {
const itemValue = item.getAttribute('data-filter-value');
const shouldShow = !filterValue || itemValue.includes(filterValue);
item.style.display = shouldShow ? '' : 'none';
});
}
// 处理数据搜索
handleDataSearch(event, element) {
const searchValue = element.value.toLowerCase();
const searchTarget = element.getAttribute('data-search');
const items = document.querySelectorAll(`[data-search-item="${searchTarget}"]`);
items.forEach(item => {
const itemText = item.textContent.toLowerCase();
const shouldShow = !searchValue || itemText.includes(searchValue);
item.style.display = shouldShow ? '' : 'none';
});
}
// 添加点击反馈
addClickFeedback(element) {
element.style.transform = 'scale(0.98)';
element.style.transition = 'transform 0.1s ease';
setTimeout(() => {
element.style.transform = '';
setTimeout(() => {
element.style.transition = '';
}, 100);
}, 100);
}
// 复制到剪贴板
async copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
this.showToast('已复制到剪贴板', 'success');
} catch (error) {
console.error('复制失败:', error);
this.showToast('复制失败', 'error');
}
}
// 显示提示
showToast(message, type = 'info') {
// 简单的提示实现
const toast = document.createElement('div');
toast.className = `alert alert-${type === 'error' ? 'danger' : type} position-fixed`;
toast.style.cssText = `
top: 20px;
right: 20px;
z-index: 9999;
min-width: 200px;
`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.remove();
}, 3000);
}
// 优化性能事件
optimizePerformanceEvents() {
// 优化滚动事件
let scrollTimer;
const optimizedScrollHandler = this.throttle(() => {
document.body.classList.add('is-scrolling');
clearTimeout(scrollTimer);
scrollTimer = setTimeout(() => {
document.body.classList.remove('is-scrolling');
}, 150);
}, 16);
window.addEventListener('scroll', optimizedScrollHandler, { passive: true });
// 优化调整大小事件
const optimizedResizeHandler = this.debounce(() => {
// 触发自定义调整大小事件
window.dispatchEvent(new CustomEvent('optimized:resize'));
}, 250);
window.addEventListener('resize', optimizedResizeHandler, { passive: true });
}
// 节流函数
throttle(func, delay) {
let timeoutId;
let lastExecTime = 0;
return function (...args) {
const currentTime = Date.now();
if (currentTime - lastExecTime > delay) {
func.apply(this, args);
lastExecTime = currentTime;
} else {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
lastExecTime = Date.now();
}, delay - (currentTime - lastExecTime));
}
};
}
// 防抖函数
debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// 移除事件委托
undelegate(eventType, selector) {
const eventKey = `${eventType}:${selector}`;
const handler = this.delegatedEvents.get(eventKey);
if (handler) {
document.removeEventListener(eventType, handler);
this.delegatedEvents.delete(eventKey);
this.eventHandlers.delete(eventKey);
}
}
// 销毁所有事件
destroy() {
this.delegatedEvents.forEach((handler, eventKey) => {
const [eventType] = eventKey.split(':');
document.removeEventListener(eventType, handler);
});
this.delegatedEvents.clear();
this.eventHandlers.clear();
}
}
// 创建全局实例
const eventDelegationManager = new EventDelegationManager();
// 导出
export default eventDelegationManager;
资源加载优化
1. 资源预加载策略
<!-- resource-preload.html -->
<!-- 资源预加载示例 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bootstrap 性能优化示例</title>
<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="dns-prefetch" href="//cdn.jsdelivr.net">
<link rel="dns-prefetch" href="//api.example.com">
<!-- 预连接重要资源 -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- 预加载关键资源 -->
<link rel="preload" href="/css/bootstrap.min.css" as="style">
<link rel="preload" href="/css/custom.css" as="style">
<link rel="preload" href="/js/bootstrap.bundle.min.js" as="script">
<link rel="preload" href="/fonts/custom-font.woff2" as="font" type="font/woff2" crossorigin>
<!-- 预加载关键图片 -->
<link rel="preload" href="/images/hero-bg.webp" as="image">
<link rel="preload" href="/images/logo.svg" as="image">
<!-- 模块预加载 -->
<link rel="modulepreload" href="/js/modules/app.js">
<link rel="modulepreload" href="/js/modules/components.js">
<!-- 关键 CSS 内联 -->
<style>
/* 关键 CSS 样式 */
.hero {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.loading-skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
</style>
<!-- 非关键 CSS 异步加载 -->
<link rel="preload" href="/css/bootstrap.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/bootstrap.min.css"></noscript>
<link rel="preload" href="/css/custom.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/custom.css"></noscript>
</head>
<body>
<!-- 页面内容 -->
<div class="hero">
<div class="container text-center text-white">
<h1 class="display-4 mb-4">Bootstrap 性能优化</h1>
<p class="lead mb-4">高性能的 Web 应用开发实践</p>
<button class="btn btn-primary btn-lg" data-lazy-modal data-bs-target="#exampleModal">
打开模态框
</button>
</div>
</div>
<!-- 懒加载内容区域 -->
<div class="container my-5">
<div class="row">
<div class="col-md-6 mb-4">
<div class="card" data-lazy-chart>
<div class="card-header">
<h5>销售图表</h5>
</div>
<div class="card-body">
<div class="loading-skeleton" style="height: 300px;"></div>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card" data-lazy-datatable>
<div class="card-header">
<h5>数据表格</h5>
</div>
<div class="card-body">
<div class="loading-skeleton" style="height: 300px;"></div>
</div>
</div>
</div>
</div>
</div>
<!-- 资源加载脚本 -->
<script>
// 资源加载管理器
class ResourceLoader {
constructor() {
this.loadedResources = new Set();
this.loadingPromises = new Map();
}
// 异步加载 CSS
loadCSS(href, media = 'all') {
if (this.loadedResources.has(href)) {
return Promise.resolve();
}
if (this.loadingPromises.has(href)) {
return this.loadingPromises.get(href);
}
const promise = new Promise((resolve, reject) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
link.media = media;
link.onload = () => {
this.loadedResources.add(href);
resolve();
};
link.onerror = () => {
reject(new Error(`Failed to load CSS: ${href}`));
};
document.head.appendChild(link);
});
this.loadingPromises.set(href, promise);
return promise;
}
// 异步加载 JavaScript
loadJS(src, type = 'text/javascript') {
if (this.loadedResources.has(src)) {
return Promise.resolve();
}
if (this.loadingPromises.has(src)) {
return this.loadingPromises.get(src);
}
const promise = new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.type = type;
script.async = true;
script.onload = () => {
this.loadedResources.add(src);
resolve();
};
script.onerror = () => {
reject(new Error(`Failed to load JS: ${src}`));
};
document.head.appendChild(script);
});
this.loadingPromises.set(src, promise);
return promise;
}
// 预加载图片
preloadImage(src) {
if (this.loadedResources.has(src)) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
this.loadedResources.add(src);
resolve();
};
img.onerror = () => {
reject(new Error(`Failed to preload image: ${src}`));
};
img.src = src;
});
}
// 批量预加载资源
async preloadResources(resources) {
const promises = resources.map(resource => {
switch (resource.type) {
case 'css':
return this.loadCSS(resource.url, resource.media);
case 'js':
return this.loadJS(resource.url, resource.scriptType);
case 'image':
return this.preloadImage(resource.url);
default:
return Promise.resolve();
}
});
return Promise.allSettled(promises);
}
}
// 创建资源加载器实例
const resourceLoader = new ResourceLoader();
// 页面加载完成后预加载非关键资源
window.addEventListener('load', () => {
const nonCriticalResources = [
{ type: 'css', url: '/css/animations.css' },
{ type: 'css', url: '/css/print.css', media: 'print' },
{ type: 'js', url: '/js/analytics.js' },
{ type: 'image', url: '/images/gallery-1.jpg' },
{ type: 'image', url: '/images/gallery-2.jpg' }
];
resourceLoader.preloadResources(nonCriticalResources)
.then(results => {
console.log('Non-critical resources loaded:', results);
});
});
</script>
<!-- 主要 JavaScript 文件 -->
<script src="/js/bootstrap.bundle.min.js" defer></script>
<script type="module" src="/js/app.js"></script>
</body>
</html>
2. 缓存策略优化
// cache-strategy.js
// 缓存策略管理器
class CacheStrategyManager {
constructor() {
this.cacheVersion = '1.0.0';
this.staticCacheName = `bootstrap-static-${this.cacheVersion}`;
this.dynamicCacheName = `bootstrap-dynamic-${this.cacheVersion}`;
this.imageCacheName = `bootstrap-images-${this.cacheVersion}`;
this.staticAssets = [
'/css/bootstrap.min.css',
'/css/custom.css',
'/js/bootstrap.bundle.min.js',
'/js/app.js',
'/fonts/custom-font.woff2',
'/images/logo.svg'
];
this.init();
}
// 初始化缓存策略
init() {
if ('serviceWorker' in navigator) {
this.registerServiceWorker();
}
// 设置内存缓存
this.setupMemoryCache();
// 设置本地存储缓存
this.setupLocalStorageCache();
}
// 注册 Service Worker
async registerServiceWorker() {
try {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('Service Worker registered:', registration);
// 监听更新
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
this.showUpdateNotification();
}
});
});
} catch (error) {
console.error('Service Worker registration failed:', error);
}
}
// 设置内存缓存
setupMemoryCache() {
this.memoryCache = new Map();
this.memoryCacheSize = 0;
this.maxMemoryCacheSize = 50 * 1024 * 1024; // 50MB
}
// 内存缓存操作
setMemoryCache(key, data, size) {
// 检查缓存大小限制
if (this.memoryCacheSize + size > this.maxMemoryCacheSize) {
this.clearOldMemoryCache();
}
this.memoryCache.set(key, {
data,
size,
timestamp: Date.now()
});
this.memoryCacheSize += size;
}
getMemoryCache(key) {
const cached = this.memoryCache.get(key);
if (cached) {
// 更新访问时间
cached.timestamp = Date.now();
return cached.data;
}
return null;
}
// 清理旧的内存缓存
clearOldMemoryCache() {
const entries = Array.from(this.memoryCache.entries());
entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
// 删除最旧的 25% 缓存
const deleteCount = Math.floor(entries.length * 0.25);
for (let i = 0; i < deleteCount; i++) {
const [key, value] = entries[i];
this.memoryCache.delete(key);
this.memoryCacheSize -= value.size;
}
}
// 设置本地存储缓存
setupLocalStorageCache() {
this.localStoragePrefix = 'bootstrap_cache_';
this.maxLocalStorageSize = 5 * 1024 * 1024; // 5MB
}
// 本地存储缓存操作
setLocalStorageCache(key, data, expiry = 24 * 60 * 60 * 1000) {
try {
const cacheData = {
data,
timestamp: Date.now(),
expiry: Date.now() + expiry
};
const serialized = JSON.stringify(cacheData);
const cacheKey = this.localStoragePrefix + key;
// 检查存储空间
if (this.getLocalStorageSize() + serialized.length > this.maxLocalStorageSize) {
this.clearOldLocalStorageCache();
}
localStorage.setItem(cacheKey, serialized);
} catch (error) {
console.warn('Local storage cache failed:', error);
}
}
getLocalStorageCache(key) {
try {
const cacheKey = this.localStoragePrefix + key;
const cached = localStorage.getItem(cacheKey);
if (cached) {
const cacheData = JSON.parse(cached);
// 检查是否过期
if (Date.now() < cacheData.expiry) {
return cacheData.data;
} else {
localStorage.removeItem(cacheKey);
}
}
} catch (error) {
console.warn('Local storage cache read failed:', error);
}
return null;
}
// 获取本地存储使用大小
getLocalStorageSize() {
let total = 0;
for (let key in localStorage) {
if (key.startsWith(this.localStoragePrefix)) {
total += localStorage[key].length;
}
}
return total;
}
// 清理旧的本地存储缓存
clearOldLocalStorageCache() {
const keys = [];
for (let key in localStorage) {
if (key.startsWith(this.localStoragePrefix)) {
try {
const data = JSON.parse(localStorage[key]);
keys.push({ key, timestamp: data.timestamp });
} catch (error) {
// 删除损坏的缓存
localStorage.removeItem(key);
}
}
}
// 按时间排序,删除最旧的
keys.sort((a, b) => a.timestamp - b.timestamp);
const deleteCount = Math.floor(keys.length * 0.3);
for (let i = 0; i < deleteCount; i++) {
localStorage.removeItem(keys[i].key);
}
}
// 显示更新通知
showUpdateNotification() {
const notification = document.createElement('div');
notification.className = 'alert alert-info position-fixed';
notification.style.cssText = `
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 9999;
min-width: 300px;
`;
notification.innerHTML = `
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<strong>应用已更新</strong><br>
<small>点击刷新以获取最新版本</small>
</div>
<button class="btn btn-sm btn-primary ms-2" onclick="window.location.reload()">
刷新
</button>
<button class="btn btn-sm btn-outline-secondary ms-1" onclick="this.parentElement.parentElement.remove()">
×
</button>
</div>
`;
document.body.appendChild(notification);
}
// 清理所有缓存
clearAllCache() {
// 清理内存缓存
this.memoryCache.clear();
this.memoryCacheSize = 0;
// 清理本地存储缓存
for (let key in localStorage) {
if (key.startsWith(this.localStoragePrefix)) {
localStorage.removeItem(key);
}
}
// 清理 Service Worker 缓存
if ('caches' in window) {
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName.startsWith('bootstrap-')) {
return caches.delete(cacheName);
}
})
);
});
}
}
}
// Service Worker 代码
const serviceWorkerCode = `
// sw.js
// Service Worker 缓存策略
const CACHE_VERSION = '1.0.0';
const STATIC_CACHE = \`bootstrap-static-\${CACHE_VERSION}\`;
const DYNAMIC_CACHE = \`bootstrap-dynamic-\${CACHE_VERSION}\`;
const IMAGE_CACHE = \`bootstrap-images-\${CACHE_VERSION}\`;
const STATIC_ASSETS = [
'/',
'/css/bootstrap.min.css',
'/css/custom.css',
'/js/bootstrap.bundle.min.js',
'/js/app.js',
'/fonts/custom-font.woff2',
'/images/logo.svg'
];
// 安装事件
self.addEventListener('install', event => {
event.waitUntil(
caches.open(STATIC_CACHE)
.then(cache => {
return cache.addAll(STATIC_ASSETS);
})
.then(() => {
return self.skipWaiting();
})
);
});
// 激活事件
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys()
.then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName.startsWith('bootstrap-') &&
cacheName !== STATIC_CACHE &&
cacheName !== DYNAMIC_CACHE &&
cacheName !== IMAGE_CACHE) {
return caches.delete(cacheName);
}
})
);
})
.then(() => {
return self.clients.claim();
})
);
});
// 拦截请求
self.addEventListener('fetch', event => {
const { request } = event;
const url = new URL(request.url);
// 静态资源:缓存优先
if (STATIC_ASSETS.includes(url.pathname)) {
event.respondWith(
caches.match(request)
.then(response => {
return response || fetch(request)
.then(fetchResponse => {
const responseClone = fetchResponse.clone();
caches.open(STATIC_CACHE)
.then(cache => {
cache.put(request, responseClone);
});
return fetchResponse;
});
})
);
return;
}
// 图片资源:缓存优先,带过期时间
if (request.destination === 'image') {
event.respondWith(
caches.match(request)
.then(response => {
if (response) {
// 检查缓存时间(7天)
const cachedDate = new Date(response.headers.get('date'));
const now = new Date();
const daysDiff = (now - cachedDate) / (1000 * 60 * 60 * 24);
if (daysDiff < 7) {
return response;
}
}
return fetch(request)
.then(fetchResponse => {
if (fetchResponse.ok) {
const responseClone = fetchResponse.clone();
caches.open(IMAGE_CACHE)
.then(cache => {
cache.put(request, responseClone);
});
}
return fetchResponse;
})
.catch(() => {
// 网络失败时返回缓存
return response;
});
})
);
return;
}
// API 请求:网络优先
if (url.pathname.startsWith('/api/')) {
event.respondWith(
fetch(request)
.then(response => {
if (response.ok) {
const responseClone = response.clone();
caches.open(DYNAMIC_CACHE)
.then(cache => {
cache.put(request, responseClone);
});
}
return response;
})
.catch(() => {
return caches.match(request);
})
);
return;
}
// 其他请求:网络优先
event.respondWith(
fetch(request)
.catch(() => {
return caches.match(request);
})
);
});
`;
// 创建全局实例
const cacheStrategyManager = new CacheStrategyManager();
// 导出
export default cacheStrategyManager;
移动端性能优化
1. 移动端特定优化
// mobile-optimization.js
// 移动端性能优化管理器
class MobileOptimizationManager {
constructor() {
this.isMobile = this.detectMobile();
this.isLowEndDevice = this.detectLowEndDevice();
this.connectionType = this.getConnectionType();
this.init();
}
// 检测移动设备
detectMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
window.innerWidth <= 768;
}
// 检测低端设备
detectLowEndDevice() {
// 基于硬件并发数和内存判断
const cores = navigator.hardwareConcurrency || 1;
const memory = navigator.deviceMemory || 1;
return cores <= 2 || memory <= 2;
}
// 获取网络连接类型
getConnectionType() {
if ('connection' in navigator) {
return navigator.connection.effectiveType || 'unknown';
}
return 'unknown';
}
// 初始化移动端优化
init() {
if (this.isMobile) {
this.optimizeForMobile();
}
if (this.isLowEndDevice) {
this.optimizeForLowEndDevice();
}
this.optimizeForConnection();
this.setupPerformanceMonitoring();
}
// 移动端优化
optimizeForMobile() {
// 禁用悬停效果
this.disableHoverEffects();
// 优化触摸事件
this.optimizeTouchEvents();
// 优化滚动性能
this.optimizeScrolling();
// 减少动画
this.reduceAnimations();
// 优化字体加载
this.optimizeFontLoading();
}
// 禁用悬停效果
disableHoverEffects() {
const style = document.createElement('style');
style.textContent = `
@media (hover: none) {
.btn:hover,
.card:hover,
.nav-link:hover,
.dropdown-item:hover {
transform: none !important;
box-shadow: none !important;
background-color: inherit !important;
}
}
`;
document.head.appendChild(style);
}
// 优化触摸事件
optimizeTouchEvents() {
// 添加触摸反馈
document.addEventListener('touchstart', (e) => {
const target = e.target.closest('.btn, .card, .nav-link');
if (target) {
target.style.opacity = '0.8';
target.style.transform = 'scale(0.98)';
}
}, { passive: true });
document.addEventListener('touchend', (e) => {
const target = e.target.closest('.btn, .card, .nav-link');
if (target) {
setTimeout(() => {
target.style.opacity = '';
target.style.transform = '';
}, 150);
}
}, { passive: true });
// 防止双击缩放
document.addEventListener('touchend', (e) => {
const now = Date.now();
if (this.lastTouchEnd && now - this.lastTouchEnd < 300) {
e.preventDefault();
}
this.lastTouchEnd = now;
}, { passive: false });
}
// 优化滚动性能
optimizeScrolling() {
// 使用 CSS 优化滚动
const style = document.createElement('style');
style.textContent = `
* {
-webkit-overflow-scrolling: touch;
}
.scroll-container {
transform: translateZ(0);
will-change: scroll-position;
}
.fixed-element {
transform: translateZ(0);
will-change: transform;
}
`;
document.head.appendChild(style);
// 添加滚动容器类
document.querySelectorAll('.overflow-auto, .overflow-scroll').forEach(el => {
el.classList.add('scroll-container');
});
// 添加固定元素类
document.querySelectorAll('.fixed-top, .fixed-bottom, .sticky-top').forEach(el => {
el.classList.add('fixed-element');
});
}
// 减少动画
reduceAnimations() {
if (this.isLowEndDevice || this.connectionType === 'slow-2g' || this.connectionType === '2g') {
const style = document.createElement('style');
style.textContent = `
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
.fade {
transition: none !important;
}
.collapse {
transition: none !important;
}
`;
document.head.appendChild(style);
}
}
// 优化字体加载
optimizeFontLoading() {
// 使用 font-display: swap
const style = document.createElement('style');
style.textContent = `
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom-font.woff2') format('woff2');
font-display: swap;
}
`;
document.head.appendChild(style);
// 预加载关键字体
if (this.connectionType !== 'slow-2g' && this.connectionType !== '2g') {
const fontLink = document.createElement('link');
fontLink.rel = 'preload';
fontLink.href = '/fonts/custom-font.woff2';
fontLink.as = 'font';
fontLink.type = 'font/woff2';
fontLink.crossOrigin = 'anonymous';
document.head.appendChild(fontLink);
}
}
// 低端设备优化
optimizeForLowEndDevice() {
// 减少 DOM 操作
this.batchDOMUpdates();
// 简化样式
this.simplifyStyles();
// 减少内存使用
this.reduceMemoryUsage();
}
// 批量 DOM 更新
batchDOMUpdates() {
this.domUpdateQueue = [];
this.domUpdateScheduled = false;
this.queueDOMUpdate = (callback) => {
this.domUpdateQueue.push(callback);
if (!this.domUpdateScheduled) {
this.domUpdateScheduled = true;
requestAnimationFrame(() => {
this.domUpdateQueue.forEach(callback => callback());
this.domUpdateQueue = [];
this.domUpdateScheduled = false;
});
}
};
}
// 简化样式
simplifyStyles() {
const style = document.createElement('style');
style.textContent = `
.card {
box-shadow: 0 1px 3px rgba(0,0,0,0.12) !important;
}
.btn {
box-shadow: none !important;
}
.dropdown-menu {
box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important;
}
.modal {
backdrop-filter: none !important;
}
`;
document.head.appendChild(style);
}
// 减少内存使用
reduceMemoryUsage() {
// 定期清理未使用的事件监听器
setInterval(() => {
this.cleanupEventListeners();
}, 60000); // 每分钟清理一次
// 限制图片质量
this.limitImageQuality();
}
// 清理事件监听器
cleanupEventListeners() {
// 移除不可见元素的事件监听器
document.querySelectorAll('[data-cleanup-listeners]').forEach(el => {
if (!el.offsetParent) {
// 元素不可见,移除事件监听器
const newEl = el.cloneNode(true);
el.parentNode.replaceChild(newEl, el);
}
});
}
// 限制图片质量
limitImageQuality() {
document.querySelectorAll('img').forEach(img => {
if (!img.dataset.optimized) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
img.onload = () => {
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
ctx.drawImage(img, 0, 0);
// 降低质量
const optimizedDataUrl = canvas.toDataURL('image/jpeg', 0.7);
img.src = optimizedDataUrl;
img.dataset.optimized = 'true';
};
}
});
}
// 网络连接优化
optimizeForConnection() {
if ('connection' in navigator) {
const connection = navigator.connection;
// 监听网络变化
connection.addEventListener('change', () => {
this.connectionType = connection.effectiveType;
this.adjustForConnection();
});
this.adjustForConnection();
}
}
// 根据网络调整
adjustForConnection() {
switch (this.connectionType) {
case 'slow-2g':
case '2g':
this.enableDataSaverMode();
break;
case '3g':
this.enableReducedQualityMode();
break;
case '4g':
default:
this.enableNormalMode();
break;
}
}
// 数据节省模式
enableDataSaverMode() {
// 禁用图片加载
document.querySelectorAll('img[data-src]').forEach(img => {
img.style.display = 'none';
});
// 禁用视频自动播放
document.querySelectorAll('video').forEach(video => {
video.autoplay = false;
video.preload = 'none';
});
// 显示数据节省提示
this.showDataSaverNotification();
}
// 降低质量模式
enableReducedQualityMode() {
// 使用低质量图片
document.querySelectorAll('img[data-src-low]').forEach(img => {
img.src = img.dataset.srcLow;
});
}
// 正常模式
enableNormalMode() {
// 恢复正常图片
document.querySelectorAll('img[data-src]').forEach(img => {
img.src = img.dataset.src;
img.style.display = '';
});
}
// 显示数据节省通知
showDataSaverNotification() {
const notification = document.createElement('div');
notification.className = 'alert alert-warning position-fixed';
notification.style.cssText = `
bottom: 20px;
left: 20px;
right: 20px;
z-index: 9999;
`;
notification.innerHTML = `
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<strong>数据节省模式</strong><br>
<small>检测到慢速网络,已启用数据节省模式</small>
</div>
<button class="btn btn-sm btn-outline-warning ms-2" onclick="this.parentElement.parentElement.remove()">
×
</button>
</div>
`;
document.body.appendChild(notification);
// 5秒后自动隐藏
setTimeout(() => {
if (notification.parentElement) {
notification.remove();
}
}, 5000);
}
// 性能监控
setupPerformanceMonitoring() {
// 监控 FPS
this.monitorFPS();
// 监控内存使用
this.monitorMemoryUsage();
// 监控网络性能
this.monitorNetworkPerformance();
}
// 监控 FPS
monitorFPS() {
let lastTime = performance.now();
let frameCount = 0;
let fps = 60;
const measureFPS = (currentTime) => {
frameCount++;
if (currentTime - lastTime >= 1000) {
fps = Math.round((frameCount * 1000) / (currentTime - lastTime));
frameCount = 0;
lastTime = currentTime;
// 如果 FPS 过低,启用性能优化
if (fps < 30) {
this.enablePerformanceMode();
}
}
requestAnimationFrame(measureFPS);
};
requestAnimationFrame(measureFPS);
}
// 监控内存使用
monitorMemoryUsage() {
if ('memory' in performance) {
setInterval(() => {
const memory = performance.memory;
const usedPercent = (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100;
// 如果内存使用超过 80%,触发清理
if (usedPercent > 80) {
this.triggerMemoryCleanup();
}
}, 10000); // 每10秒检查一次
}
}
// 监控网络性能
monitorNetworkPerformance() {
// 使用 Navigation Timing API
window.addEventListener('load', () => {
const navigation = performance.getEntriesByType('navigation')[0];
const loadTime = navigation.loadEventEnd - navigation.loadEventStart;
// 如果加载时间过长,启用优化
if (loadTime > 3000) {
this.enableSlowNetworkOptimizations();
}
});
}
// 启用性能模式
enablePerformanceMode() {
if (!this.performanceModeEnabled) {
this.performanceModeEnabled = true;
// 减少动画
this.reduceAnimations();
// 降低渲染质量
document.body.style.transform = 'translateZ(0)';
document.body.style.backfaceVisibility = 'hidden';
}
}
// 触发内存清理
triggerMemoryCleanup() {
// 清理缓存
if (window.cacheStrategyManager) {
window.cacheStrategyManager.clearOldMemoryCache();
}
// 强制垃圾回收(如果支持)
if (window.gc) {
window.gc();
}
}
// 慢速网络优化
enableSlowNetworkOptimizations() {
// 延迟加载非关键资源
this.deferNonCriticalResources();
// 压缩图片
this.compressImages();
}
// 延迟加载非关键资源
deferNonCriticalResources() {
// 延迟加载分析脚本
setTimeout(() => {
const analyticsScript = document.createElement('script');
analyticsScript.src = '/js/analytics.js';
analyticsScript.async = true;
document.head.appendChild(analyticsScript);
}, 5000);
}
// 压缩图片
compressImages() {
document.querySelectorAll('img').forEach(img => {
if (!img.dataset.compressed) {
// 添加压缩参数
if (img.src.includes('?')) {
img.src += '&quality=70&format=webp';
} else {
img.src += '?quality=70&format=webp';
}
img.dataset.compressed = 'true';
}
});
}
}
// 创建全局实例
const mobileOptimizationManager = new MobileOptimizationManager();
// 导出
export default mobileOptimizationManager;
最佳实践与开发规范
1. 性能开发规范
// performance-guidelines.js
// 性能开发规范和最佳实践
class PerformanceGuidelines {
constructor() {
this.performanceRules = {
// CSS 规范
css: {
maxFileSize: 100 * 1024, // 100KB
maxSelectors: 4000,
maxNestingDepth: 4,
avoidExpensiveSelectors: [
'*',
'[attribute^="value"]',
':nth-child(n+3)',
'div > div > div > div'
]
},
// JavaScript 规范
javascript: {
maxFileSize: 200 * 1024, // 200KB
maxFunctionComplexity: 10,
maxDOMQueries: 5,
avoidPatterns: [
'document.write',
'eval',
'with',
'innerHTML in loops'
]
},
// 图片规范
images: {
maxFileSize: 500 * 1024, // 500KB
preferredFormats: ['webp', 'avif', 'jpg', 'png'],
maxDimensions: { width: 1920, height: 1080 },
compressionQuality: 0.8
},
// 网络规范
network: {
maxRequests: 50,
maxTotalSize: 2 * 1024 * 1024, // 2MB
timeToFirstByte: 200, // ms
firstContentfulPaint: 1500 // ms
}
};
this.init();
}
// 初始化性能检查
init() {
this.setupPerformanceObserver();
this.setupResourceMonitoring();
this.setupUserTimingAPI();
this.createPerformanceDashboard();
}
// 设置性能观察器
setupPerformanceObserver() {
if ('PerformanceObserver' in window) {
// 监控 LCP (Largest Contentful Paint)
const lcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
this.recordMetric('LCP', lastEntry.startTime);
});
lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });
// 监控 FID (First Input Delay)
const fidObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
this.recordMetric('FID', entry.processingStart - entry.startTime);
});
});
fidObserver.observe({ entryTypes: ['first-input'] });
// 监控 CLS (Cumulative Layout Shift)
const clsObserver = new PerformanceObserver((list) => {
let clsValue = 0;
const entries = list.getEntries();
entries.forEach(entry => {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
});
this.recordMetric('CLS', clsValue);
});
clsObserver.observe({ entryTypes: ['layout-shift'] });
// 监控长任务
const longTaskObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
this.recordMetric('Long Task', entry.duration);
this.analyzeLongTask(entry);
});
});
longTaskObserver.observe({ entryTypes: ['longtask'] });
}
}
// 设置资源监控
setupResourceMonitoring() {
// 监控资源加载
const resourceObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
this.analyzeResource(entry);
});
});
resourceObserver.observe({ entryTypes: ['resource'] });
// 监控导航时间
window.addEventListener('load', () => {
const navigation = performance.getEntriesByType('navigation')[0];
this.analyzeNavigation(navigation);
});
}
// 设置用户时间 API
setupUserTimingAPI() {
this.timingMarks = new Map();
// 创建便捷方法
window.performanceMark = (name) => {
performance.mark(name);
this.timingMarks.set(name, performance.now());
};
window.performanceMeasure = (name, startMark, endMark) => {
performance.measure(name, startMark, endMark);
const measure = performance.getEntriesByName(name, 'measure')[0];
this.recordMetric(name, measure.duration);
};
}
// 分析长任务
analyzeLongTask(entry) {
const taskInfo = {
duration: entry.duration,
startTime: entry.startTime,
attribution: entry.attribution
};
// 如果任务超过 100ms,记录警告
if (entry.duration > 100) {
console.warn('Long task detected:', taskInfo);
this.reportPerformanceIssue('long-task', taskInfo);
}
}
// 分析资源
analyzeResource(entry) {
const resourceInfo = {
name: entry.name,
type: this.getResourceType(entry),
size: entry.transferSize || entry.encodedBodySize,
duration: entry.duration,
startTime: entry.startTime
};
// 检查资源大小
this.checkResourceSize(resourceInfo);
// 检查加载时间
this.checkResourceTiming(resourceInfo);
// 检查缓存效率
this.checkCacheEfficiency(entry);
}
// 获取资源类型
getResourceType(entry) {
if (entry.initiatorType) {
return entry.initiatorType;
}
const url = new URL(entry.name);
const extension = url.pathname.split('.').pop().toLowerCase();
const typeMap = {
'css': 'stylesheet',
'js': 'script',
'jpg': 'image',
'jpeg': 'image',
'png': 'image',
'gif': 'image',
'webp': 'image',
'svg': 'image',
'woff': 'font',
'woff2': 'font',
'ttf': 'font',
'eot': 'font'
};
return typeMap[extension] || 'other';
}
// 检查资源大小
checkResourceSize(resourceInfo) {
const { type, size, name } = resourceInfo;
let maxSize;
switch (type) {
case 'stylesheet':
maxSize = this.performanceRules.css.maxFileSize;
break;
case 'script':
maxSize = this.performanceRules.javascript.maxFileSize;
break;
case 'image':
maxSize = this.performanceRules.images.maxFileSize;
break;
default:
return;
}
if (size > maxSize) {
this.reportPerformanceIssue('large-resource', {
name,
type,
size,
maxSize,
recommendation: this.getOptimizationRecommendation(type)
});
}
}
// 检查资源时间
checkResourceTiming(resourceInfo) {
const { duration, name, type } = resourceInfo;
// 设置不同资源类型的时间阈值
const timeThresholds = {
'stylesheet': 500,
'script': 1000,
'image': 2000,
'font': 1000
};
const threshold = timeThresholds[type] || 1000;
if (duration > threshold) {
this.reportPerformanceIssue('slow-resource', {
name,
type,
duration,
threshold,
recommendation: 'Consider optimizing or using CDN'
});
}
}
// 检查缓存效率
checkCacheEfficiency(entry) {
const transferSize = entry.transferSize || 0;
const encodedBodySize = entry.encodedBodySize || 0;
// 如果传输大小为 0,说明从缓存加载
const fromCache = transferSize === 0 && encodedBodySize > 0;
// 如果传输大小远小于编码大小,说明使用了压缩
const compressionRatio = transferSize / encodedBodySize;
this.recordMetric('Cache Hit', fromCache ? 1 : 0);
if (!fromCache && compressionRatio > 0.8) {
this.reportPerformanceIssue('poor-compression', {
name: entry.name,
compressionRatio,
recommendation: 'Enable gzip/brotli compression'
});
}
}
// 分析导航时间
analyzeNavigation(navigation) {
const timings = {
'DNS Lookup': navigation.domainLookupEnd - navigation.domainLookupStart,
'TCP Connection': navigation.connectEnd - navigation.connectStart,
'TLS Handshake': navigation.secureConnectionStart > 0 ?
navigation.connectEnd - navigation.secureConnectionStart : 0,
'Request': navigation.responseStart - navigation.requestStart,
'Response': navigation.responseEnd - navigation.responseStart,
'DOM Processing': navigation.domContentLoadedEventStart - navigation.responseEnd,
'Resource Loading': navigation.loadEventStart - navigation.domContentLoadedEventEnd
};
Object.entries(timings).forEach(([name, duration]) => {
this.recordMetric(name, duration);
});
// 检查关键时间指标
const ttfb = navigation.responseStart - navigation.requestStart;
if (ttfb > this.performanceRules.network.timeToFirstByte) {
this.reportPerformanceIssue('slow-ttfb', {
ttfb,
threshold: this.performanceRules.network.timeToFirstByte,
recommendation: 'Optimize server response time'
});
}
}
// 获取优化建议
getOptimizationRecommendation(type) {
const recommendations = {
'stylesheet': 'Use CSS minification, remove unused styles, consider critical CSS',
'script': 'Use JavaScript minification, code splitting, tree shaking',
'image': 'Use modern formats (WebP, AVIF), optimize compression, implement lazy loading',
'font': 'Use font-display: swap, subset fonts, preload critical fonts'
};
return recommendations[type] || 'Consider optimization techniques';
}
// 记录性能指标
recordMetric(name, value) {
if (!this.metrics) {
this.metrics = new Map();
}
if (!this.metrics.has(name)) {
this.metrics.set(name, []);
}
this.metrics.get(name).push({
value,
timestamp: Date.now()
});
// 更新仪表板
this.updateDashboard(name, value);
}
// 报告性能问题
reportPerformanceIssue(type, details) {
const issue = {
type,
details,
timestamp: Date.now(),
severity: this.getSeverity(type, details)
};
console.warn('Performance issue detected:', issue);
// 发送到分析服务
this.sendToAnalytics(issue);
// 显示开发者通知
if (this.isDevelopmentMode()) {
this.showDeveloperNotification(issue);
}
}
// 获取问题严重程度
getSeverity(type, details) {
const severityMap = {
'long-task': details.duration > 500 ? 'high' : 'medium',
'large-resource': details.size > details.maxSize * 2 ? 'high' : 'medium',
'slow-resource': details.duration > details.threshold * 2 ? 'high' : 'medium',
'poor-compression': 'low',
'slow-ttfb': details.ttfb > 1000 ? 'high' : 'medium'
};
return severityMap[type] || 'low';
}
// 检查是否为开发模式
isDevelopmentMode() {
return location.hostname === 'localhost' ||
location.hostname === '127.0.0.1' ||
location.hostname.includes('dev');
}
// 显示开发者通知
showDeveloperNotification(issue) {
const notification = document.createElement('div');
notification.className = `alert alert-${this.getSeverityClass(issue.severity)} position-fixed`;
notification.style.cssText = `
top: 20px;
right: 20px;
max-width: 400px;
z-index: 10000;
font-size: 12px;
`;
notification.innerHTML = `
<div class="d-flex align-items-start">
<div class="flex-grow-1">
<strong>Performance Issue: ${issue.type}</strong><br>
<small>${JSON.stringify(issue.details, null, 2)}</small>
</div>
<button class="btn btn-sm btn-outline-secondary ms-2" onclick="this.parentElement.parentElement.remove()">
×
</button>
</div>
`;
document.body.appendChild(notification);
// 10秒后自动隐藏
setTimeout(() => {
if (notification.parentElement) {
notification.remove();
}
}, 10000);
}
// 获取严重程度对应的 Bootstrap 类
getSeverityClass(severity) {
const classMap = {
'high': 'danger',
'medium': 'warning',
'low': 'info'
};
return classMap[severity] || 'info';
}
// 发送到分析服务
sendToAnalytics(issue) {
// 这里可以集成 Google Analytics、Sentry 等服务
if (typeof gtag !== 'undefined') {
gtag('event', 'performance_issue', {
event_category: 'Performance',
event_label: issue.type,
value: issue.severity === 'high' ? 3 : issue.severity === 'medium' ? 2 : 1
});
}
}
// 创建性能仪表板
createPerformanceDashboard() {
if (!this.isDevelopmentMode()) {
return;
}
const dashboard = document.createElement('div');
dashboard.id = 'performance-dashboard';
dashboard.style.cssText = `
position: fixed;
bottom: 20px;
left: 20px;
width: 300px;
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 15px;
border-radius: 8px;
font-family: monospace;
font-size: 12px;
z-index: 10000;
max-height: 400px;
overflow-y: auto;
display: none;
`;
dashboard.innerHTML = `
<div class="d-flex justify-content-between align-items-center mb-2">
<strong>Performance Dashboard</strong>
<button class="btn btn-sm btn-outline-light" onclick="this.parentElement.parentElement.style.display='none'">
×
</button>
</div>
<div id="performance-metrics"></div>
`;
document.body.appendChild(dashboard);
// 添加切换按钮
const toggleButton = document.createElement('button');
toggleButton.textContent = '📊';
toggleButton.style.cssText = `
position: fixed;
bottom: 20px;
left: 20px;
width: 40px;
height: 40px;
border-radius: 50%;
border: none;
background: #007bff;
color: white;
font-size: 16px;
cursor: pointer;
z-index: 10001;
`;
toggleButton.onclick = () => {
dashboard.style.display = dashboard.style.display === 'none' ? 'block' : 'none';
};
document.body.appendChild(toggleButton);
this.dashboard = dashboard;
}
// 更新仪表板
updateDashboard(name, value) {
if (!this.dashboard) {
return;
}
const metricsContainer = this.dashboard.querySelector('#performance-metrics');
let metricElement = metricsContainer.querySelector(`[data-metric="${name}"]`);
if (!metricElement) {
metricElement = document.createElement('div');
metricElement.setAttribute('data-metric', name);
metricElement.style.marginBottom = '5px';
metricsContainer.appendChild(metricElement);
}
const formattedValue = typeof value === 'number' ?
(value < 1 ? value.toFixed(3) : Math.round(value)) : value;
metricElement.innerHTML = `<strong>${name}:</strong> ${formattedValue}${this.getUnit(name)}`;
}
// 获取指标单位
getUnit(metricName) {
const units = {
'LCP': 'ms',
'FID': 'ms',
'CLS': '',
'Long Task': 'ms',
'DNS Lookup': 'ms',
'TCP Connection': 'ms',
'TLS Handshake': 'ms',
'Request': 'ms',
'Response': 'ms',
'DOM Processing': 'ms',
'Resource Loading': 'ms',
'Cache Hit': ''
};
return units[metricName] || '';
}
// 生成性能报告
generateReport() {
const report = {
timestamp: new Date().toISOString(),
metrics: {},
summary: {},
recommendations: []
};
// 汇总指标
this.metrics.forEach((values, name) => {
const numericValues = values.map(v => v.value).filter(v => typeof v === 'number');
if (numericValues.length > 0) {
report.metrics[name] = {
count: numericValues.length,
average: numericValues.reduce((a, b) => a + b, 0) / numericValues.length,
min: Math.min(...numericValues),
max: Math.max(...numericValues),
latest: values[values.length - 1].value
};
}
});
// 生成摘要
report.summary = this.generateSummary(report.metrics);
// 生成建议
report.recommendations = this.generateRecommendations(report.metrics);
return report;
}
// 生成摘要
generateSummary(metrics) {
const summary = {
performance: 'good',
issues: [],
score: 100
};
// 检查核心 Web 指标
if (metrics.LCP && metrics.LCP.latest > 2500) {
summary.issues.push('LCP is too slow');
summary.score -= 20;
}
if (metrics.FID && metrics.FID.latest > 100) {
summary.issues.push('FID is too high');
summary.score -= 15;
}
if (metrics.CLS && metrics.CLS.latest > 0.1) {
summary.issues.push('CLS is too high');
summary.score -= 15;
}
// 确定整体性能等级
if (summary.score >= 90) {
summary.performance = 'excellent';
} else if (summary.score >= 70) {
summary.performance = 'good';
} else if (summary.score >= 50) {
summary.performance = 'needs improvement';
} else {
summary.performance = 'poor';
}
return summary;
}
// 生成建议
generateRecommendations(metrics) {
const recommendations = [];
if (metrics.LCP && metrics.LCP.latest > 2500) {
recommendations.push({
priority: 'high',
category: 'LCP',
suggestion: 'Optimize largest contentful paint by reducing server response time, optimizing images, and removing render-blocking resources'
});
}
if (metrics['Long Task'] && metrics['Long Task'].average > 50) {
recommendations.push({
priority: 'medium',
category: 'JavaScript',
suggestion: 'Break up long tasks by using code splitting and yielding to the main thread'
});
}
if (metrics['Cache Hit'] && metrics['Cache Hit'].average < 0.8) {
recommendations.push({
priority: 'medium',
category: 'Caching',
suggestion: 'Improve cache hit ratio by implementing proper cache headers and service worker caching'
});
}
return recommendations;
}
}
// 创建全局实例
const performanceGuidelines = new PerformanceGuidelines();
// 导出
export default performanceGuidelines;
2. 代码审查清单
# Bootstrap 性能优化代码审查清单
## CSS 优化检查
- [ ] 是否移除了未使用的 CSS 规则?
- [ ] 是否使用了 CSS 压缩?
- [ ] 是否避免了复杂的选择器?
- [ ] 是否使用了关键 CSS 内联?
- [ ] 是否合理使用了 CSS 自定义属性?
- [ ] 是否避免了 @import 语句?
- [ ] 是否使用了适当的 CSS 单位(rem, em, %)?
## JavaScript 优化检查
- [ ] 是否实现了代码分割?
- [ ] 是否使用了懒加载?
- [ ] 是否避免了全局变量污染?
- [ ] 是否使用了事件委托?
- [ ] 是否实现了防抖和节流?
- [ ] 是否移除了未使用的代码?
- [ ] 是否使用了现代 JavaScript 特性?
## 图片优化检查
- [ ] 是否使用了现代图片格式(WebP, AVIF)?
- [ ] 是否实现了响应式图片?
- [ ] 是否使用了图片懒加载?
- [ ] 是否优化了图片压缩质量?
- [ ] 是否提供了适当的图片尺寸?
- [ ] 是否使用了 SVG 替代简单图标?
## 网络优化检查
- [ ] 是否启用了 HTTP/2?
- [ ] 是否使用了 CDN?
- [ ] 是否实现了资源预加载?
- [ ] 是否启用了 Gzip/Brotli 压缩?
- [ ] 是否设置了适当的缓存头?
- [ ] 是否减少了 HTTP 请求数量?
## 移动端优化检查
- [ ] 是否实现了触摸友好的交互?
- [ ] 是否优化了滚动性能?
- [ ] 是否考虑了低端设备性能?
- [ ] 是否实现了网络自适应?
- [ ] 是否禁用了不必要的悬停效果?
## 可访问性检查
- [ ] 是否提供了适当的 ARIA 标签?
- [ ] 是否支持键盘导航?
- [ ] 是否有足够的颜色对比度?
- [ ] 是否提供了替代文本?
- [ ] 是否支持屏幕阅读器?
性能监控与分析
1. 性能监控工具集成
// performance-monitoring.js
// 性能监控工具集成
class PerformanceMonitoring {
constructor() {
this.config = {
// Google Analytics 配置
ga: {
trackingId: 'GA_TRACKING_ID',
enabled: true
},
// Web Vitals 配置
webVitals: {
enabled: true,
reportAllChanges: false
},
// 自定义监控配置
custom: {
enabled: true,
sampleRate: 0.1, // 10% 采样率
endpoint: '/api/performance'
}
};
this.init();
}
// 初始化监控
async init() {
// 加载 Web Vitals 库
if (this.config.webVitals.enabled) {
await this.loadWebVitals();
}
// 设置自定义监控
if (this.config.custom.enabled) {
this.setupCustomMonitoring();
}
// 设置错误监控
this.setupErrorMonitoring();
// 设置用户行为监控
this.setupUserBehaviorMonitoring();
}
// 加载 Web Vitals
async loadWebVitals() {
try {
const { getCLS, getFID, getFCP, getLCP, getTTFB } = await import('web-vitals');
// 监控 CLS (Cumulative Layout Shift)
getCLS(this.sendToAnalytics.bind(this), this.config.webVitals.reportAllChanges);
// 监控 FID (First Input Delay)
getFID(this.sendToAnalytics.bind(this));
// 监控 FCP (First Contentful Paint)
getFCP(this.sendToAnalytics.bind(this));
// 监控 LCP (Largest Contentful Paint)
getLCP(this.sendToAnalytics.bind(this), this.config.webVitals.reportAllChanges);
// 监控 TTFB (Time to First Byte)
getTTFB(this.sendToAnalytics.bind(this));
} catch (error) {
console.warn('Failed to load Web Vitals:', error);
}
}
// 设置自定义监控
setupCustomMonitoring() {
// 监控页面加载时间
window.addEventListener('load', () => {
const navigation = performance.getEntriesByType('navigation')[0];
const loadTime = navigation.loadEventEnd - navigation.loadEventStart;
this.sendCustomMetric('page_load_time', loadTime);
});
// 监控资源加载
const resourceObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
if (Math.random() < this.config.custom.sampleRate) {
this.sendCustomMetric('resource_load_time', {
name: entry.name,
duration: entry.duration,
size: entry.transferSize
});
}
});
});
resourceObserver.observe({ entryTypes: ['resource'] });
// 监控用户交互
this.setupInteractionMonitoring();
}
// 设置交互监控
setupInteractionMonitoring() {
// 监控点击事件
document.addEventListener('click', (event) => {
const target = event.target;
const selector = this.getElementSelector(target);
this.sendCustomMetric('user_interaction', {
type: 'click',
selector,
timestamp: Date.now()
});
}, { passive: true });
// 监控表单提交
document.addEventListener('submit', (event) => {
const form = event.target;
const formId = form.id || form.className;
this.sendCustomMetric('form_submission', {
formId,
timestamp: Date.now()
});
}, { passive: true });
}
// 设置错误监控
setupErrorMonitoring() {
// JavaScript 错误
window.addEventListener('error', (event) => {
this.sendErrorReport({
type: 'javascript_error',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error ? event.error.stack : null
});
});
// Promise 拒绝
window.addEventListener('unhandledrejection', (event) => {
this.sendErrorReport({
type: 'unhandled_promise_rejection',
reason: event.reason,
stack: event.reason ? event.reason.stack : null
});
});
// 资源加载错误
document.addEventListener('error', (event) => {
if (event.target !== window) {
this.sendErrorReport({
type: 'resource_error',
element: event.target.tagName,
source: event.target.src || event.target.href,
message: 'Resource failed to load'
});
}
}, true);
}
// 设置用户行为监控
setupUserBehaviorMonitoring() {
// 页面可见性变化
document.addEventListener('visibilitychange', () => {
this.sendCustomMetric('page_visibility', {
hidden: document.hidden,
timestamp: Date.now()
});
});
// 页面卸载
window.addEventListener('beforeunload', () => {
const sessionDuration = Date.now() - this.sessionStartTime;
this.sendCustomMetric('session_duration', sessionDuration);
});
// 记录会话开始时间
this.sessionStartTime = Date.now();
}
// 获取元素选择器
getElementSelector(element) {
if (element.id) {
return `#${element.id}`;
}
if (element.className) {
return `.${element.className.split(' ')[0]}`;
}
return element.tagName.toLowerCase();
}
// 发送到分析服务
sendToAnalytics(metric) {
// 发送到 Google Analytics
if (this.config.ga.enabled && typeof gtag !== 'undefined') {
gtag('event', 'web_vitals', {
event_category: 'Web Vitals',
event_label: metric.name,
value: Math.round(metric.value),
custom_map: {
metric_id: metric.id,
metric_value: metric.value,
metric_delta: metric.delta
}
});
}
// 发送到自定义端点
this.sendCustomMetric('web_vitals', {
name: metric.name,
value: metric.value,
id: metric.id,
delta: metric.delta
});
}
// 发送自定义指标
sendCustomMetric(name, data) {
if (!this.config.custom.enabled) {
return;
}
const payload = {
metric: name,
data,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
sessionId: this.getSessionId()
};
// 使用 sendBeacon 或 fetch 发送
if (navigator.sendBeacon) {
navigator.sendBeacon(
this.config.custom.endpoint,
JSON.stringify(payload)
);
} else {
fetch(this.config.custom.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload),
keepalive: true
}).catch(error => {
console.warn('Failed to send metric:', error);
});
}
}
// 发送错误报告
sendErrorReport(error) {
const payload = {
...error,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
sessionId: this.getSessionId()
};
// 发送到错误监控服务
this.sendCustomMetric('error', payload);
// 发送到 Google Analytics
if (this.config.ga.enabled && typeof gtag !== 'undefined') {
gtag('event', 'exception', {
description: error.message || error.type,
fatal: false
});
}
}
// 获取会话 ID
getSessionId() {
if (!this.sessionId) {
this.sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
return this.sessionId;
}
// 手动记录性能标记
mark(name) {
performance.mark(name);
this.sendCustomMetric('performance_mark', {
name,
timestamp: performance.now()
});
}
// 手动记录性能测量
measure(name, startMark, endMark) {
performance.measure(name, startMark, endMark);
const measure = performance.getEntriesByName(name, 'measure')[0];
this.sendCustomMetric('performance_measure', {
name,
duration: measure.duration,
startTime: measure.startTime
});
}
}
// 创建全局实例
const performanceMonitoring = new PerformanceMonitoring();
// 导出便捷方法
window.perfMark = performanceMonitoring.mark.bind(performanceMonitoring);
window.perfMeasure = performanceMonitoring.measure.bind(performanceMonitoring);
// 导出
export default performanceMonitoring;
本章总结
本章深入探讨了 Bootstrap 应用的性能优化策略和最佳实践,涵盖了以下关键领域:
核心优化技术
CSS 性能优化
- 使用 PurgeCSS 移除未使用的样式
- 自定义 Bootstrap 构建以减少文件大小
- 实现关键 CSS 内联和异步加载
- CSS 压缩和优化技术
JavaScript 性能优化
- 按需加载 Bootstrap 组件
- 代码分割和懒加载策略
- 事件委托和性能优化
- 内存管理和清理
资源加载优化
- 资源预加载和预连接
- 缓存策略和 Service Worker
- 图片优化和现代格式支持
- 字体加载优化
移动端优化
移动设备适配
- 低端设备性能优化
- 网络连接自适应
- 触摸交互优化
- 电池和内存使用优化
响应式性能
- 移动优先的加载策略
- 数据节省模式
- 自适应图片和媒体
- 滚动性能优化
监控和分析
性能监控
- Core Web Vitals 监控
- 自定义性能指标
- 错误监控和报告
- 用户行为分析
开发工具
- 性能仪表板
- 代码审查清单
- 自动化性能检测
- 持续集成优化
最佳实践
- 开发规范
- 性能预算设定
- 代码质量标准
- 测试和验证流程
- 团队协作规范
通过实施这些优化策略,可以显著提升 Bootstrap 应用的性能表现,改善用户体验,并确保应用在各种设备和网络条件下都能良好运行。
练习题
基础练习
CSS 优化实践
- 使用 PurgeCSS 优化一个 Bootstrap 项目
- 实现关键 CSS 内联
- 测量优化前后的文件大小差异
JavaScript 懒加载
- 实现 Bootstrap 组件的按需加载
- 创建一个懒加载管理器
- 测量首屏加载时间的改善
缓存策略
- 实现 Service Worker 缓存
- 配置不同类型资源的缓存策略
- 测试离线功能
进阶练习
移动端优化
- 实现网络自适应加载
- 创建低端设备优化模式
- 测试不同网络条件下的性能
性能监控
- 集成 Web Vitals 监控
- 创建自定义性能仪表板
- 实现性能预警系统
综合优化项目
- 选择一个现有的 Bootstrap 项目
- 应用本章所有优化技术
- 生成详细的性能报告
- 制定持续优化计划
实战项目
- 企业级性能优化
- 为大型 Bootstrap 应用制定性能优化方案
- 实现自动化性能测试
- 建立性能监控和报警系统
- 制定性能优化的 CI/CD 流程
通过这些练习,你将能够熟练掌握 Bootstrap 应用的性能优化技术,并能够在实际项目中应用这些最佳实践。