1. 性能监控
1.1 性能指标监控
// utils/performance.js
class PerformanceMonitor {
constructor() {
this.metrics = {
pageLoadTime: 0,
firstPaint: 0,
firstContentfulPaint: 0,
largestContentfulPaint: 0,
memoryUsage: 0,
networkRequests: [],
errors: []
}
this.observers = []
this.init()
}
// 初始化性能监控
init() {
this.monitorPageLoad()
this.monitorMemory()
this.monitorNetwork()
this.monitorErrors()
// #ifdef H5
this.monitorWebVitals()
// #endif
}
// 监控页面加载时间
monitorPageLoad() {
const startTime = Date.now()
// 页面加载完成时记录
uni.$on('pageReady', () => {
this.metrics.pageLoadTime = Date.now() - startTime
this.reportMetric('pageLoadTime', this.metrics.pageLoadTime)
})
}
// 监控内存使用
monitorMemory() {
// #ifdef H5
if (performance.memory) {
setInterval(() => {
this.metrics.memoryUsage = {
used: performance.memory.usedJSHeapSize,
total: performance.memory.totalJSHeapSize,
limit: performance.memory.jsHeapSizeLimit
}
// 内存使用率超过80%时警告
const usageRate = this.metrics.memoryUsage.used / this.metrics.memoryUsage.total
if (usageRate > 0.8) {
console.warn('内存使用率过高:', usageRate)
this.reportMetric('highMemoryUsage', usageRate)
}
}, 5000)
}
// #endif
// #ifdef APP-PLUS
setInterval(() => {
plus.runtime.getProperty(plus.runtime.appid, (info) => {
this.metrics.memoryUsage = {
used: info.memory || 0
}
})
}, 5000)
// #endif
}
// 监控网络请求
monitorNetwork() {
const originalRequest = uni.request
uni.request = (options) => {
const startTime = Date.now()
const requestInfo = {
url: options.url,
method: options.method || 'GET',
startTime,
endTime: 0,
duration: 0,
success: false,
statusCode: 0,
size: 0
}
const originalSuccess = options.success
const originalFail = options.fail
const originalComplete = options.complete
options.success = (res) => {
requestInfo.endTime = Date.now()
requestInfo.duration = requestInfo.endTime - requestInfo.startTime
requestInfo.success = true
requestInfo.statusCode = res.statusCode
requestInfo.size = JSON.stringify(res.data).length
this.metrics.networkRequests.push(requestInfo)
// 慢请求警告(超过3秒)
if (requestInfo.duration > 3000) {
console.warn('慢请求检测:', requestInfo)
this.reportMetric('slowRequest', requestInfo)
}
if (originalSuccess) originalSuccess(res)
}
options.fail = (err) => {
requestInfo.endTime = Date.now()
requestInfo.duration = requestInfo.endTime - requestInfo.startTime
requestInfo.success = false
requestInfo.error = err
this.metrics.networkRequests.push(requestInfo)
this.reportMetric('requestError', requestInfo)
if (originalFail) originalFail(err)
}
options.complete = (res) => {
// 保持网络请求记录在合理范围内
if (this.metrics.networkRequests.length > 100) {
this.metrics.networkRequests = this.metrics.networkRequests.slice(-50)
}
if (originalComplete) originalComplete(res)
}
return originalRequest(options)
}
}
// 监控错误
monitorErrors() {
// Vue错误处理
const app = getApp()
if (app && app.globalData && app.globalData.Vue) {
app.globalData.Vue.config.errorHandler = (err, vm, info) => {
const errorInfo = {
type: 'vue',
message: err.message,
stack: err.stack,
info,
timestamp: Date.now(),
url: getCurrentPages().pop()?.route || 'unknown'
}
this.metrics.errors.push(errorInfo)
this.reportMetric('error', errorInfo)
console.error('Vue错误:', errorInfo)
}
}
// Promise错误处理
// #ifdef H5
window.addEventListener('unhandledrejection', (event) => {
const errorInfo = {
type: 'promise',
message: event.reason?.message || event.reason,
stack: event.reason?.stack,
timestamp: Date.now(),
url: window.location.href
}
this.metrics.errors.push(errorInfo)
this.reportMetric('error', errorInfo)
console.error('Promise错误:', errorInfo)
})
// #endif
}
// 监控Web Vitals(H5专用)
monitorWebVitals() {
// #ifdef H5
if (typeof PerformanceObserver !== 'undefined') {
// First Paint
const paintObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-paint') {
this.metrics.firstPaint = entry.startTime
} else if (entry.name === 'first-contentful-paint') {
this.metrics.firstContentfulPaint = entry.startTime
}
}
})
paintObserver.observe({ entryTypes: ['paint'] })
// Largest Contentful Paint
const lcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries()
const lastEntry = entries[entries.length - 1]
this.metrics.largestContentfulPaint = lastEntry.startTime
})
lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] })
this.observers.push(paintObserver, lcpObserver)
}
// #endif
}
// 上报性能指标
reportMetric(type, data) {
// 这里可以上报到性能监控平台
console.log(`性能指标 [${type}]:`, data)
// 示例:上报到自定义服务器
// uni.request({
// url: 'https://api.example.com/metrics',
// method: 'POST',
// data: {
// type,
// data,
// timestamp: Date.now(),
// userAgent: navigator.userAgent,
// appVersion: getApp().globalData.version
// }
// })
}
// 获取性能报告
getPerformanceReport() {
const report = {
...this.metrics,
timestamp: Date.now(),
deviceInfo: uni.getSystemInfoSync()
}
// 计算网络请求统计
const requests = this.metrics.networkRequests
if (requests.length > 0) {
report.networkStats = {
total: requests.length,
success: requests.filter(r => r.success).length,
failed: requests.filter(r => !r.success).length,
averageDuration: requests.reduce((sum, r) => sum + r.duration, 0) / requests.length,
slowRequests: requests.filter(r => r.duration > 3000).length
}
}
return report
}
// 清理监控器
destroy() {
this.observers.forEach(observer => {
if (observer.disconnect) {
observer.disconnect()
}
})
this.observers = []
}
}
// 创建性能监控实例
const performanceMonitor = new PerformanceMonitor()
export default performanceMonitor
1.2 FPS监控
// utils/fpsMonitor.js
class FPSMonitor {
constructor() {
this.fps = 0
this.lastTime = 0
this.frameCount = 0
this.isRunning = false
this.callbacks = []
this.history = []
this.maxHistory = 60 // 保存60秒的历史数据
}
// 开始监控
start() {
if (this.isRunning) return
this.isRunning = true
this.lastTime = Date.now()
this.frameCount = 0
this.tick()
}
// 停止监控
stop() {
this.isRunning = false
}
// 帧计算
tick() {
if (!this.isRunning) return
this.frameCount++
const currentTime = Date.now()
// 每秒计算一次FPS
if (currentTime - this.lastTime >= 1000) {
this.fps = Math.round((this.frameCount * 1000) / (currentTime - this.lastTime))
// 保存历史数据
this.history.push({
fps: this.fps,
timestamp: currentTime
})
// 限制历史数据长度
if (this.history.length > this.maxHistory) {
this.history.shift()
}
// 通知回调
this.callbacks.forEach(callback => {
try {
callback(this.fps)
} catch (error) {
console.error('FPS回调执行失败:', error)
}
})
// 低FPS警告
if (this.fps < 30) {
console.warn('FPS过低:', this.fps)
}
this.frameCount = 0
this.lastTime = currentTime
}
// 使用requestAnimationFrame或setTimeout
if (typeof requestAnimationFrame !== 'undefined') {
requestAnimationFrame(() => this.tick())
} else {
setTimeout(() => this.tick(), 16) // 约60FPS
}
}
// 添加FPS变化回调
onFPSChange(callback) {
this.callbacks.push(callback)
}
// 移除FPS变化回调
offFPSChange(callback) {
const index = this.callbacks.indexOf(callback)
if (index > -1) {
this.callbacks.splice(index, 1)
}
}
// 获取当前FPS
getCurrentFPS() {
return this.fps
}
// 获取平均FPS
getAverageFPS() {
if (this.history.length === 0) return 0
const sum = this.history.reduce((total, item) => total + item.fps, 0)
return Math.round(sum / this.history.length)
}
// 获取最低FPS
getMinFPS() {
if (this.history.length === 0) return 0
return Math.min(...this.history.map(item => item.fps))
}
// 获取最高FPS
getMaxFPS() {
if (this.history.length === 0) return 0
return Math.max(...this.history.map(item => item.fps))
}
// 获取FPS统计
getStats() {
return {
current: this.getCurrentFPS(),
average: this.getAverageFPS(),
min: this.getMinFPS(),
max: this.getMaxFPS(),
history: this.history.slice()
}
}
// 重置统计
reset() {
this.fps = 0
this.frameCount = 0
this.history = []
this.lastTime = Date.now()
}
}
// 创建FPS监控实例
const fpsMonitor = new FPSMonitor()
export default fpsMonitor
2. 代码优化
2.1 组件优化
<!-- 优化前的组件 -->
<template>
<view class="product-list">
<view
v-for="product in products"
:key="product.id"
class="product-item"
@click="onProductClick(product)"
>
<image :src="product.image" mode="aspectFill" />
<view class="product-info">
<text class="product-name">{{ product.name }}</text>
<text class="product-price">¥{{ formatPrice(product.price) }}</text>
<text class="product-sales">已售{{ product.sales }}件</text>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
products: {
type: Array,
default: () => []
}
},
methods: {
// 每次渲染都会执行,性能差
formatPrice(price) {
return (price / 100).toFixed(2)
},
onProductClick(product) {
uni.navigateTo({
url: `/pages/product/detail?id=${product.id}`
})
}
}
}
</script>
<!-- 优化后的组件 -->
<template>
<view class="product-list">
<view
v-for="product in optimizedProducts"
:key="product.id"
class="product-item"
@click="onProductClick(product.id)"
>
<image
:src="product.image"
mode="aspectFill"
:lazy-load="true"
@error="onImageError"
/>
<view class="product-info">
<text class="product-name">{{ product.name }}</text>
<text class="product-price">¥{{ product.formattedPrice }}</text>
<text class="product-sales">已售{{ product.sales }}件</text>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
products: {
type: Array,
default: () => []
}
},
computed: {
// 使用计算属性预处理数据
optimizedProducts() {
return this.products.map(product => ({
...product,
formattedPrice: this.formatPrice(product.price)
}))
}
},
methods: {
formatPrice(price) {
return (price / 100).toFixed(2)
},
// 避免传递整个对象,减少内存占用
onProductClick(productId) {
uni.navigateTo({
url: `/pages/product/detail?id=${productId}`
})
},
onImageError(e) {
// 图片加载失败处理
console.log('图片加载失败:', e)
}
}
}
</script>
2.2 列表优化
<!-- 虚拟列表组件 -->
<template>
<scroll-view
class="virtual-list"
:scroll-y="true"
:style="{ height: containerHeight + 'px' }"
@scroll="onScroll"
:scroll-top="scrollTop"
>
<!-- 占位元素,撑开滚动高度 -->
<view :style="{ height: totalHeight + 'px', position: 'relative' }">
<!-- 可见区域的列表项 -->
<view
v-for="item in visibleItems"
:key="item.id"
class="list-item"
:style="{
position: 'absolute',
top: item.top + 'px',
left: 0,
right: 0,
height: itemHeight + 'px'
}"
>
<slot :item="item.data" :index="item.index"></slot>
</view>
</view>
</scroll-view>
</template>
<script>
export default {
name: 'VirtualList',
props: {
items: {
type: Array,
default: () => []
},
itemHeight: {
type: Number,
default: 100
},
containerHeight: {
type: Number,
default: 600
},
bufferSize: {
type: Number,
default: 5
}
},
data() {
return {
scrollTop: 0,
startIndex: 0,
endIndex: 0
}
},
computed: {
totalHeight() {
return this.items.length * this.itemHeight
},
visibleCount() {
return Math.ceil(this.containerHeight / this.itemHeight)
},
visibleItems() {
const start = Math.max(0, this.startIndex - this.bufferSize)
const end = Math.min(this.items.length, this.endIndex + this.bufferSize)
const items = []
for (let i = start; i < end; i++) {
items.push({
id: this.items[i].id || i,
index: i,
data: this.items[i],
top: i * this.itemHeight
})
}
return items
}
},
watch: {
items: {
handler() {
this.updateVisibleRange()
},
immediate: true
}
},
methods: {
onScroll(e) {
this.scrollTop = e.detail.scrollTop
this.updateVisibleRange()
},
updateVisibleRange() {
this.startIndex = Math.floor(this.scrollTop / this.itemHeight)
this.endIndex = this.startIndex + this.visibleCount
},
scrollToIndex(index) {
this.scrollTop = index * this.itemHeight
}
}
}
</script>
<style lang="scss" scoped>
.virtual-list {
overflow: hidden;
}
.list-item {
box-sizing: border-box;
}
</style>
2.3 图片优化
// utils/imageOptimizer.js
class ImageOptimizer {
constructor() {
this.cache = new Map()
this.loadingQueue = new Map()
this.maxCacheSize = 50
this.defaultPlaceholder = '/static/images/placeholder.png'
}
// 获取优化后的图片URL
getOptimizedImageUrl(originalUrl, options = {}) {
if (!originalUrl) return this.defaultPlaceholder
const {
width = 0,
height = 0,
quality = 80,
format = 'webp',
mode = 'aspectFill'
} = options
// 如果是本地图片,直接返回
if (originalUrl.startsWith('/static') || originalUrl.startsWith('data:')) {
return originalUrl
}
// 生成缓存key
const cacheKey = `${originalUrl}_${width}_${height}_${quality}_${format}`
// 检查缓存
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)
}
// 构建优化URL(根据实际图片服务调整)
let optimizedUrl = originalUrl
// 示例:阿里云OSS图片处理
if (originalUrl.includes('aliyuncs.com')) {
const params = []
if (width && height) {
params.push(`resize,m_fill,w_${width},h_${height}`)
} else if (width) {
params.push(`resize,w_${width}`)
} else if (height) {
params.push(`resize,h_${height}`)
}
if (quality < 100) {
params.push(`quality,q_${quality}`)
}
if (format && format !== 'jpg') {
params.push(`format,${format}`)
}
if (params.length > 0) {
optimizedUrl = `${originalUrl}?x-oss-process=image/${params.join('/')}`
}
}
// 示例:腾讯云COS图片处理
else if (originalUrl.includes('myqcloud.com')) {
const params = []
if (width && height) {
params.push(`thumbnail/${width}x${height}`)
}
if (quality < 100) {
params.push(`quality/${quality}`)
}
if (format && format !== 'jpg') {
params.push(`format/${format}`)
}
if (params.length > 0) {
optimizedUrl = `${originalUrl}?imageMogr2/${params.join('/')}`
}
}
// 缓存结果
this.setCache(cacheKey, optimizedUrl)
return optimizedUrl
}
// 预加载图片
preloadImage(url, options = {}) {
const optimizedUrl = this.getOptimizedImageUrl(url, options)
return new Promise((resolve, reject) => {
// 检查是否已在加载队列中
if (this.loadingQueue.has(optimizedUrl)) {
this.loadingQueue.get(optimizedUrl).then(resolve).catch(reject)
return
}
// 创建加载Promise
const loadPromise = new Promise((res, rej) => {
const image = new Image()
image.onload = () => {
this.loadingQueue.delete(optimizedUrl)
res(optimizedUrl)
}
image.onerror = () => {
this.loadingQueue.delete(optimizedUrl)
rej(new Error(`图片加载失败: ${optimizedUrl}`))
}
image.src = optimizedUrl
})
this.loadingQueue.set(optimizedUrl, loadPromise)
loadPromise.then(resolve).catch(reject)
})
}
// 批量预加载
async preloadImages(urls, options = {}) {
const promises = urls.map(url => this.preloadImage(url, options))
try {
const results = await Promise.allSettled(promises)
const successful = results.filter(r => r.status === 'fulfilled').length
const failed = results.filter(r => r.status === 'rejected').length
console.log(`图片预加载完成: 成功${successful}张, 失败${failed}张`)
return results
} catch (error) {
console.error('批量预加载失败:', error)
throw error
}
}
// 设置缓存
setCache(key, value) {
// 如果缓存已满,删除最旧的
if (this.cache.size >= this.maxCacheSize) {
const firstKey = this.cache.keys().next().value
this.cache.delete(firstKey)
}
this.cache.set(key, value)
}
// 清理缓存
clearCache() {
this.cache.clear()
this.loadingQueue.clear()
}
// 获取缓存统计
getCacheStats() {
return {
cacheSize: this.cache.size,
loadingCount: this.loadingQueue.size,
maxCacheSize: this.maxCacheSize
}
}
}
// 创建图片优化器实例
const imageOptimizer = new ImageOptimizer()
export default imageOptimizer
2.4 内存优化
// utils/memoryManager.js
class MemoryManager {
constructor() {
this.watchers = []
this.cleanupTasks = []
this.memoryThreshold = 0.8 // 80%内存使用率阈值
this.init()
}
// 初始化内存管理
init() {
this.startMemoryMonitoring()
this.setupPageCleanup()
}
// 开始内存监控
startMemoryMonitoring() {
setInterval(() => {
this.checkMemoryUsage()
}, 10000) // 每10秒检查一次
}
// 检查内存使用情况
checkMemoryUsage() {
// #ifdef H5
if (performance.memory) {
const { usedJSHeapSize, totalJSHeapSize } = performance.memory
const usageRate = usedJSHeapSize / totalJSHeapSize
if (usageRate > this.memoryThreshold) {
console.warn('内存使用率过高,开始清理:', usageRate)
this.performCleanup()
}
}
// #endif
}
// 执行内存清理
performCleanup() {
// 清理图片缓存
this.clearImageCache()
// 清理数据缓存
this.clearDataCache()
// 执行自定义清理任务
this.runCleanupTasks()
// 强制垃圾回收(如果支持)
this.forceGarbageCollection()
}
// 清理图片缓存
clearImageCache() {
try {
// 清理uni-app图片缓存
uni.clearStorageSync()
// 清理自定义图片缓存
if (window.imageOptimizer) {
window.imageOptimizer.clearCache()
}
console.log('图片缓存已清理')
} catch (error) {
console.error('清理图片缓存失败:', error)
}
}
// 清理数据缓存
clearDataCache() {
try {
// 清理过期的本地存储
const keys = uni.getStorageInfoSync().keys
const now = Date.now()
keys.forEach(key => {
if (key.startsWith('cache_')) {
try {
const data = uni.getStorageSync(key)
if (data && data.expireTime && data.expireTime < now) {
uni.removeStorageSync(key)
}
} catch (error) {
// 忽略解析错误,直接删除
uni.removeStorageSync(key)
}
}
})
console.log('数据缓存已清理')
} catch (error) {
console.error('清理数据缓存失败:', error)
}
}
// 运行清理任务
runCleanupTasks() {
this.cleanupTasks.forEach(task => {
try {
task()
} catch (error) {
console.error('清理任务执行失败:', error)
}
})
}
// 强制垃圾回收
forceGarbageCollection() {
// #ifdef H5
if (window.gc) {
window.gc()
console.log('已执行垃圾回收')
}
// #endif
}
// 设置页面清理
setupPageCleanup() {
// 监听页面隐藏事件
uni.$on('onHide', () => {
this.performLightCleanup()
})
// 监听页面卸载事件
uni.$on('onUnload', () => {
this.performPageCleanup()
})
}
// 轻量级清理(页面隐藏时)
performLightCleanup() {
// 清理定时器
this.clearTimers()
// 暂停动画
this.pauseAnimations()
}
// 页面清理(页面卸载时)
performPageCleanup() {
// 清理事件监听器
this.clearEventListeners()
// 清理定时器
this.clearTimers()
// 清理观察者
this.clearObservers()
}
// 清理定时器
clearTimers() {
// 这里需要应用层配合,记录所有定时器ID
const app = getApp()
if (app && app.globalData && app.globalData.timers) {
app.globalData.timers.forEach(timerId => {
clearTimeout(timerId)
clearInterval(timerId)
})
app.globalData.timers = []
}
}
// 暂停动画
pauseAnimations() {
// 暂停CSS动画
const animatedElements = document.querySelectorAll('.animate')
animatedElements.forEach(el => {
el.style.animationPlayState = 'paused'
})
}
// 清理事件监听器
clearEventListeners() {
this.watchers.forEach(watcher => {
if (typeof watcher === 'function') {
watcher()
}
})
this.watchers = []
}
// 清理观察者
clearObservers() {
// 清理Intersection Observer
if (window.intersectionObservers) {
window.intersectionObservers.forEach(observer => {
observer.disconnect()
})
window.intersectionObservers = []
}
// 清理Mutation Observer
if (window.mutationObservers) {
window.mutationObservers.forEach(observer => {
observer.disconnect()
})
window.mutationObservers = []
}
}
// 注册清理任务
registerCleanupTask(task) {
if (typeof task === 'function') {
this.cleanupTasks.push(task)
}
}
// 注册观察者
registerWatcher(watcher) {
if (typeof watcher === 'function') {
this.watchers.push(watcher)
}
}
// 获取内存使用情况
getMemoryUsage() {
// #ifdef H5
if (performance.memory) {
return {
used: performance.memory.usedJSHeapSize,
total: performance.memory.totalJSHeapSize,
limit: performance.memory.jsHeapSizeLimit,
usageRate: performance.memory.usedJSHeapSize / performance.memory.totalJSHeapSize
}
}
// #endif
return null
}
}
// 创建内存管理器实例
const memoryManager = new MemoryManager()
export default memoryManager
3. 调试技巧
3.1 调试工具
// utils/debugger.js
class Debugger {
constructor() {
this.isDebugMode = false
this.logs = []
this.maxLogs = 1000
this.filters = {
level: ['log', 'warn', 'error'],
category: []
}
this.init()
}
// 初始化调试器
init() {
// 检查是否为调试模式
this.isDebugMode = this.checkDebugMode()
if (this.isDebugMode) {
this.setupConsoleProxy()
this.setupErrorHandler()
this.setupNetworkMonitor()
}
}
// 检查调试模式
checkDebugMode() {
// #ifdef H5
return location.hostname === 'localhost' ||
location.hostname === '127.0.0.1' ||
location.search.includes('debug=true')
// #endif
// #ifdef APP-PLUS
return plus.runtime.isDebugMode
// #endif
// #ifdef MP
return wx.getAccountInfoSync().miniProgram.envVersion === 'develop'
// #endif
return false
}
// 设置控制台代理
setupConsoleProxy() {
const originalConsole = {
log: console.log,
warn: console.warn,
error: console.error,
info: console.info
}
Object.keys(originalConsole).forEach(level => {
console[level] = (...args) => {
// 记录日志
this.addLog(level, args)
// 调用原始方法
originalConsole[level].apply(console, args)
}
})
}
// 设置错误处理
setupErrorHandler() {
// #ifdef H5
window.addEventListener('error', (event) => {
this.addLog('error', [{
type: 'javascript',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack
}])
})
window.addEventListener('unhandledrejection', (event) => {
this.addLog('error', [{
type: 'promise',
message: event.reason?.message || event.reason,
stack: event.reason?.stack
}])
})
// #endif
}
// 设置网络监控
setupNetworkMonitor() {
const originalRequest = uni.request
uni.request = (options) => {
const startTime = Date.now()
this.addLog('info', [{
type: 'network',
action: 'request',
url: options.url,
method: options.method || 'GET',
data: options.data,
timestamp: startTime
}])
const originalSuccess = options.success
const originalFail = options.fail
options.success = (res) => {
this.addLog('info', [{
type: 'network',
action: 'response',
url: options.url,
statusCode: res.statusCode,
data: res.data,
duration: Date.now() - startTime,
timestamp: Date.now()
}])
if (originalSuccess) originalSuccess(res)
}
options.fail = (err) => {
this.addLog('error', [{
type: 'network',
action: 'error',
url: options.url,
error: err,
duration: Date.now() - startTime,
timestamp: Date.now()
}])
if (originalFail) originalFail(err)
}
return originalRequest(options)
}
}
// 添加日志
addLog(level, args) {
const log = {
id: Date.now() + Math.random(),
level,
args,
timestamp: Date.now(),
stack: new Error().stack
}
this.logs.push(log)
// 限制日志数量
if (this.logs.length > this.maxLogs) {
this.logs = this.logs.slice(-this.maxLogs / 2)
}
// 发送到调试面板
this.sendToDebugPanel(log)
}
// 发送到调试面板
sendToDebugPanel(log) {
uni.$emit('debugLog', log)
}
// 获取过滤后的日志
getFilteredLogs() {
return this.logs.filter(log => {
// 级别过滤
if (!this.filters.level.includes(log.level)) {
return false
}
// 分类过滤
if (this.filters.category.length > 0) {
const category = log.args[0]?.type
if (!this.filters.category.includes(category)) {
return false
}
}
return true
})
}
// 设置过滤器
setFilter(type, values) {
if (this.filters[type]) {
this.filters[type] = values
}
}
// 清空日志
clearLogs() {
this.logs = []
}
// 导出日志
exportLogs() {
const data = {
logs: this.logs,
timestamp: Date.now(),
userAgent: navigator.userAgent,
deviceInfo: uni.getSystemInfoSync()
}
return JSON.stringify(data, null, 2)
}
// 性能标记
mark(name) {
if (this.isDebugMode) {
console.time(name)
this.addLog('info', [{
type: 'performance',
action: 'mark',
name,
timestamp: Date.now()
}])
}
}
// 性能测量
measure(name) {
if (this.isDebugMode) {
console.timeEnd(name)
this.addLog('info', [{
type: 'performance',
action: 'measure',
name,
timestamp: Date.now()
}])
}
}
// 断言
assert(condition, message) {
if (!condition) {
const error = new Error(message || 'Assertion failed')
this.addLog('error', [{
type: 'assertion',
message: error.message,
stack: error.stack
}])
throw error
}
}
// 调试信息
debug(category, data) {
if (this.isDebugMode) {
this.addLog('log', [{
type: 'debug',
category,
data,
timestamp: Date.now()
}])
}
}
}
// 创建调试器实例
const debugger = new Debugger()
export default debugger
3.2 调试面板组件
<!-- components/DebugPanel.vue -->
<template>
<view v-if="visible" class="debug-panel">
<view class="debug-header">
<text class="debug-title">调试面板</text>
<view class="debug-actions">
<button @click="clearLogs" size="mini">清空</button>
<button @click="exportLogs" size="mini">导出</button>
<button @click="close" size="mini">关闭</button>
</view>
</view>
<view class="debug-filters">
<view class="filter-group">
<text class="filter-label">级别:</text>
<checkbox-group @change="onLevelChange">
<label v-for="level in levels" :key="level">
<checkbox :value="level" :checked="selectedLevels.includes(level)" />
<text>{{ level }}</text>
</label>
</checkbox-group>
</view>
</view>
<scroll-view class="debug-logs" scroll-y>
<view
v-for="log in filteredLogs"
:key="log.id"
class="log-item"
:class="`log-${log.level}`"
>
<view class="log-header">
<text class="log-time">{{ formatTime(log.timestamp) }}</text>
<text class="log-level">{{ log.level.toUpperCase() }}</text>
</view>
<view class="log-content">
<text>{{ formatLogContent(log.args) }}</text>
</view>
<view v-if="log.stack" class="log-stack">
<text>{{ log.stack }}</text>
</view>
</view>
</scroll-view>
<view class="debug-stats">
<text>总日志: {{ logs.length }}</text>
<text>内存: {{ memoryUsage }}</text>
<text>FPS: {{ currentFPS }}</text>
</view>
</view>
<!-- 调试按钮 -->
<view v-if="!visible" class="debug-trigger" @click="show">
<text class="debug-icon">🐛</text>
</view>
</template>
<script>
import debugger from '@/utils/debugger.js'
import fpsMonitor from '@/utils/fpsMonitor.js'
import memoryManager from '@/utils/memoryManager.js'
export default {
name: 'DebugPanel',
data() {
return {
visible: false,
logs: [],
levels: ['log', 'info', 'warn', 'error'],
selectedLevels: ['log', 'info', 'warn', 'error'],
currentFPS: 0,
memoryUsage: '0MB'
}
},
computed: {
filteredLogs() {
return this.logs.filter(log =>
this.selectedLevels.includes(log.level)
).slice(-100) // 只显示最近100条
}
},
mounted() {
this.init()
},
methods: {
init() {
// 监听调试日志
uni.$on('debugLog', this.onDebugLog)
// 监听FPS变化
fpsMonitor.onFPSChange(this.onFPSChange)
fpsMonitor.start()
// 定期更新内存使用情况
setInterval(() => {
this.updateMemoryUsage()
}, 5000)
},
show() {
this.visible = true
this.logs = debugger.getFilteredLogs()
},
close() {
this.visible = false
},
onDebugLog(log) {
this.logs.push(log)
// 限制日志数量
if (this.logs.length > 1000) {
this.logs = this.logs.slice(-500)
}
},
onFPSChange(fps) {
this.currentFPS = fps
},
updateMemoryUsage() {
const usage = memoryManager.getMemoryUsage()
if (usage) {
this.memoryUsage = `${Math.round(usage.used / 1024 / 1024)}MB`
}
},
onLevelChange(e) {
this.selectedLevels = e.detail.value
},
clearLogs() {
this.logs = []
debugger.clearLogs()
},
exportLogs() {
const data = debugger.exportLogs()
// #ifdef H5
const blob = new Blob([data], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `debug-logs-${Date.now()}.json`
a.click()
URL.revokeObjectURL(url)
// #endif
// #ifdef APP-PLUS
uni.showModal({
title: '导出日志',
content: '日志已复制到剪贴板',
showCancel: false
})
uni.setClipboardData({
data
})
// #endif
},
formatTime(timestamp) {
const date = new Date(timestamp)
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}`
},
formatLogContent(args) {
return args.map(arg => {
if (typeof arg === 'object') {
return JSON.stringify(arg, null, 2)
}
return String(arg)
}).join(' ')
}
},
beforeDestroy() {
uni.$off('debugLog', this.onDebugLog)
fpsMonitor.offFPSChange(this.onFPSChange)
fpsMonitor.stop()
}
}
</script>
<style lang="scss" scoped>
.debug-panel {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.9);
color: #fff;
z-index: 9999;
display: flex;
flex-direction: column;
}
.debug-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
border-bottom: 1rpx solid #333;
}
.debug-title {
font-size: 32rpx;
font-weight: bold;
}
.debug-actions {
display: flex;
gap: 10rpx;
}
.debug-filters {
padding: 20rpx;
border-bottom: 1rpx solid #333;
}
.filter-group {
display: flex;
align-items: center;
gap: 20rpx;
}
.filter-label {
font-size: 28rpx;
}
.debug-logs {
flex: 1;
padding: 20rpx;
}
.log-item {
margin-bottom: 20rpx;
padding: 15rpx;
border-radius: 8rpx;
background-color: rgba(255, 255, 255, 0.1);
&.log-error {
background-color: rgba(255, 0, 0, 0.2);
}
&.log-warn {
background-color: rgba(255, 165, 0, 0.2);
}
&.log-info {
background-color: rgba(0, 123, 255, 0.2);
}
}
.log-header {
display: flex;
justify-content: space-between;
margin-bottom: 10rpx;
}
.log-time {
font-size: 24rpx;
color: #ccc;
}
.log-level {
font-size: 24rpx;
font-weight: bold;
}
.log-content {
font-size: 26rpx;
word-break: break-all;
}
.log-stack {
margin-top: 10rpx;
font-size: 22rpx;
color: #999;
white-space: pre-wrap;
}
.debug-stats {
display: flex;
justify-content: space-around;
padding: 20rpx;
border-top: 1rpx solid #333;
font-size: 24rpx;
}
.debug-trigger {
position: fixed;
top: 100rpx;
right: 20rpx;
width: 80rpx;
height: 80rpx;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
z-index: 9998;
}
.debug-icon {
font-size: 40rpx;
}
</style>
4. 总结
性能优化与调试是UniApp开发的重要环节:
- 性能监控:实时监控应用性能指标,及时发现问题
- 代码优化:通过组件优化、列表虚拟化、图片优化等提升性能
- 内存管理:合理管理内存使用,避免内存泄漏
- 调试工具:使用专业的调试工具快速定位问题
- 性能测试:定期进行性能测试,确保应用质量
- 最佳实践:遵循性能优化的最佳实践
- 持续优化:建立性能优化的持续改进机制