性能优化技巧 - 让你的小程序跑得更快
⚡ 性能是用户体验的基础,今天我们来学习如何让小程序跑得更快更流畅
1. 代码层面优化
减少不必要的计算
vue
<template>
<view class="optimization-demo">
<!-- ❌ 错误:每次渲染都会计算 -->
<text>\{\{ expensiveCalculation() \}\}</text>
<!-- ✅ 正确:使用计算属性 -->
<text>\{\{ expensiveResult \}\}</text>
<!-- ❌ 错误:在模板中进行复杂操作 -->
<view v-for="item in items.filter(i => i.active).sort((a,b) => a.order - b.order)" :key="item.id">
\{\{ item.name \}\}
</view>
<!-- ✅ 正确:使用计算属性预处理 -->
<view v-for="item in sortedActiveItems" :key="item.id">
\{\{ item.name \}\}
</view>
</view>
</template>
<script>
export default {
data() {
return {
items: [
{ id: 1, name: '项目1', active: true, order: 2 },
{ id: 2, name: '项目2', active: false, order: 1 },
{ id: 3, name: '项目3', active: true, order: 3 }
],
cachedResult: null
}
},
computed: {
// 使用计算属性缓存复杂计算
expensiveResult() {
if (this.cachedResult !== null) {
return this.cachedResult
}
console.log('执行复杂计算...')
let result = 0
for (let i = 0; i < 1000000; i++) {
result += Math.random()
}
this.cachedResult = result
return result
},
// 预处理列表数据
sortedActiveItems() {
return this.items
.filter(item => item.active)
.sort((a, b) => a.order - b.order)
}
},
methods: {
// ❌ 避免在模板中调用方法
expensiveCalculation() {
console.log('每次渲染都会执行...')
let result = 0
for (let i = 0; i < 1000000; i++) {
result += Math.random()
}
return result
}
}
}
</script>
合理使用v-if和v-show
vue
<template>
<view class="conditional-rendering">
<!-- 频繁切换使用v-show -->
<view v-show="showPanel" class="panel">
<text>频繁切换的面板</text>
</view>
<!-- 条件很少改变使用v-if -->
<view v-if="userRole === 'admin'" class="admin-panel">
<text>管理员面板</text>
</view>
<!-- 大型组件的条件渲染 -->
<heavy-component v-if="shouldLoadHeavyComponent" />
</view>
</template>
<script>
export default {
data() {
return {
showPanel: false,
userRole: 'user',
shouldLoadHeavyComponent: false
}
},
methods: {
togglePanel() {
// 频繁切换,v-show性能更好
this.showPanel = !this.showPanel
},
loadHeavyComponent() {
// 大型组件延迟加载
this.shouldLoadHeavyComponent = true
}
}
}
</script>
防抖和节流
javascript
// utils/performance.js
export function debounce(func, wait) {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
export function throttle(func, limit) {
let inThrottle
return function(...args) {
if (!inThrottle) {
func.apply(this, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
vue
<template>
<view class="performance-demo">
<!-- 搜索防抖 -->
<input
v-model="searchKeyword"
@input="debouncedSearch"
placeholder="搜索..."
/>
<!-- 滚动节流 -->
<scroll-view
scroll-y
@scroll="throttledScroll"
class="scroll-area"
>
<view v-for="item in searchResults" :key="item.id" class="item">
\{\{ item.name \}\}
</view>
</scroll-view>
</view>
</template>
<script>
import { debounce, throttle } from '@/utils/performance.js'
export default {
data() {
return {
searchKeyword: '',
searchResults: [],
scrollTop: 0
}
},
created() {
// 创建防抖和节流函数
this.debouncedSearch = debounce(this.performSearch, 300)
this.throttledScroll = throttle(this.handleScroll, 100)
},
methods: {
performSearch() {
if (!this.searchKeyword.trim()) {
this.searchResults = []
return
}
console.log('执行搜索:', this.searchKeyword)
// 模拟搜索API调用
this.searchResults = Array.from({ length: 50 }, (_, i) => ({
id: i,
name: `搜索结果 \${i + 1} - \${this.searchKeyword}`
}))
},
handleScroll(e) {
this.scrollTop = e.detail.scrollTop
console.log('滚动位置:', this.scrollTop)
}
}
}
</script>
2. 资源优化
图片优化
vue
<template>
<view class="image-optimization">
<!-- 使用合适的图片格式和尺寸 -->
<image
:src="optimizedImageUrl"
mode="aspectFill"
lazy-load
@load="onImageLoad"
@error="onImageError"
class="optimized-image"
/>
<!-- 图片预加载 -->
<view class="image-gallery">
<image
v-for="(img, index) in images"
:key="index"
:src="img.thumbnail"
@click="previewImage(index)"
class="thumbnail"
/>
</view>
</view>
</template>
<script>
export default {
data() {
return {
images: [
{
thumbnail: '/static/thumb1.jpg',
original: '/static/original1.jpg'
},
{
thumbnail: '/static/thumb2.jpg',
original: '/static/original2.jpg'
}
],
imageCache: new Map()
}
},
computed: {
optimizedImageUrl() {
// 根据设备像素比选择合适的图片
const dpr = uni.getSystemInfoSync().pixelRatio
const baseUrl = '/static/product'
if (dpr >= 3) {
return `\${baseUrl}@3x.jpg`
} else if (dpr >= 2) {
return `\${baseUrl}@2x.jpg`
} else {
return `\${baseUrl}.jpg`
}
}
},
methods: {
onImageLoad(e) {
console.log('图片加载成功')
},
onImageError(e) {
console.log('图片加载失败,使用默认图片')
// 可以设置默认图片
},
previewImage(index) {
// 预加载原图
this.preloadImage(this.images[index].original).then(() => {
uni.previewImage({
current: index,
urls: this.images.map(img => img.original)
})
})
},
preloadImage(url) {
if (this.imageCache.has(url)) {
return Promise.resolve()
}
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => {
this.imageCache.set(url, true)
resolve()
}
img.onerror = reject
img.src = url
})
}
}
}
</script>
<style>
.optimized-image {
width: 100%;
height: 400rpx;
}
.image-gallery {
display: flex;
gap: 10rpx;
margin-top: 20rpx;
}
.thumbnail {
width: 150rpx;
height: 150rpx;
border-radius: 8rpx;
}
</style>
代码分割和懒加载
javascript
// 路由懒加载
const routes = [
{
path: '/pages/home/home',
component: () => import('@/pages/home/home.vue')
},
{
path: '/pages/profile/profile',
component: () => import('@/pages/profile/profile.vue')
}
]
// 组件懒加载
export default {
components: {
HeavyComponent: () => import('@/components/HeavyComponent.vue')
}
}
3. 渲染优化
虚拟列表
vue
<template>
<view class="virtual-list">
<scroll-view
scroll-y
:scroll-top="scrollTop"
@scroll="onScroll"
class="scroll-container"
:style="{ height: containerHeight + 'px' }"
>
<!-- 占位元素,撑开滚动高度 -->
<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'
}"
>
<text>\{\{ item.data.name \}\}</text>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
items: [], // 所有数据
scrollTop: 0,
containerHeight: 600, // 容器高度
itemHeight: 80, // 每项高度
visibleCount: 8, // 可见项数量
bufferCount: 2 // 缓冲项数量
}
},
computed: {
totalHeight() {
return this.items.length * this.itemHeight
},
startIndex() {
const index = Math.floor(this.scrollTop / this.itemHeight)
return Math.max(0, index - this.bufferCount)
},
endIndex() {
const index = this.startIndex + this.visibleCount + this.bufferCount * 2
return Math.min(this.items.length - 1, index)
},
visibleItems() {
const items = []
for (let i = this.startIndex; i <= this.endIndex; i++) {
if (this.items[i]) {
items.push({
id: this.items[i].id,
data: this.items[i],
top: i * this.itemHeight
})
}
}
return items
}
},
created() {
this.generateData()
},
methods: {
generateData() {
// 生成大量数据
this.items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `项目 \${i + 1}`,
value: Math.random()
}))
},
onScroll(e) {
this.scrollTop = e.detail.scrollTop
}
}
}
</script>
<style>
.virtual-list {
height: 100vh;
}
.scroll-container {
height: 100%;
}
.list-item {
display: flex;
align-items: center;
padding: 0 20rpx;
border-bottom: 1rpx solid #eee;
background: white;
}
</style>
减少DOM操作
vue
<template>
<view class="dom-optimization">
<!-- ❌ 错误:频繁的DOM操作 -->
<view class="bad-example">
<view
v-for="item in items"
:key="item.id"
:style="getBadStyle(item)"
class="item"
>
\{\{ item.name \}\}
</view>
</view>
<!-- ✅ 正确:批量处理样式 -->
<view class="good-example">
<view
v-for="item in itemsWithStyles"
:key="item.id"
:class="item.className"
:style="item.style"
class="item"
>
\{\{ item.name \}\}
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
items: [
{ id: 1, name: '项目1', priority: 'high' },
{ id: 2, name: '项目2', priority: 'medium' },
{ id: 3, name: '项目3', priority: 'low' }
]
}
},
computed: {
// 预计算样式,避免在渲染时计算
itemsWithStyles() {
return this.items.map(item => ({
...item,
className: `priority-\${item.priority}`,
style: this.getOptimizedStyle(item)
}))
}
},
methods: {
// ❌ 每次渲染都会调用
getBadStyle(item) {
return {
backgroundColor: item.priority === 'high' ? '#ff6b6b' :
item.priority === 'medium' ? '#ffa726' : '#66bb6a',
color: 'white',
padding: '10px'
}
},
// ✅ 预计算样式
getOptimizedStyle(item) {
const colorMap = {
high: '#ff6b6b',
medium: '#ffa726',
low: '#66bb6a'
}
return {
backgroundColor: colorMap[item.priority],
color: 'white',
padding: '10px'
}
}
}
}
</script>
<style>
.item {
margin: 10rpx 0;
border-radius: 8rpx;
}
/* 使用CSS类而不是内联样式 */
.priority-high {
background-color: #ff6b6b !important;
}
.priority-medium {
background-color: #ffa726 !important;
}
.priority-low {
background-color: #66bb6a !important;
}
</style>
4. 网络优化
请求优化
javascript
// utils/requestOptimizer.js
class RequestOptimizer {
constructor() {
this.cache = new Map()
this.pendingRequests = new Map()
}
// 请求缓存
async cachedRequest(url, options = {}) {
const cacheKey = this.getCacheKey(url, options)
// 检查缓存
if (this.cache.has(cacheKey)) {
const cached = this.cache.get(cacheKey)
if (Date.now() - cached.timestamp < (options.cacheTime || 300000)) {
return cached.data
}
}
// 检查是否有相同的请求正在进行
if (this.pendingRequests.has(cacheKey)) {
return this.pendingRequests.get(cacheKey)
}
// 发起新请求
const requestPromise = this.makeRequest(url, options)
this.pendingRequests.set(cacheKey, requestPromise)
try {
const result = await requestPromise
// 缓存结果
this.cache.set(cacheKey, {
data: result,
timestamp: Date.now()
})
return result
} finally {
this.pendingRequests.delete(cacheKey)
}
}
// 批量请求
async batchRequest(requests) {
const promises = requests.map(req => this.cachedRequest(req.url, req.options))
return Promise.all(promises)
}
// 请求重试
async retryRequest(url, options = {}, maxRetries = 3) {
let lastError
for (let i = 0; i <= maxRetries; i++) {
try {
return await this.makeRequest(url, options)
} catch (error) {
lastError = error
if (i < maxRetries) {
// 指数退避
const delay = Math.pow(2, i) * 1000
await new Promise(resolve => setTimeout(resolve, delay))
}
}
}
throw lastError
}
async makeRequest(url, options) {
const response = await uni.request({
url,
...options
})
if (response.statusCode !== 200) {
throw new Error(`HTTP \${response.statusCode}`)
}
return response.data
}
getCacheKey(url, options) {
return `\${url}_\${JSON.stringify(options.data || {})}`
}
clearCache() {
this.cache.clear()
}
}
export default new RequestOptimizer()
5. 内存优化
避免内存泄漏
vue
<template>
<view class="memory-optimization">
<text>\{\{ currentTime \}\}</text>
<button @click="startTimer">开始定时器</button>
<button @click="stopTimer">停止定时器</button>
</view>
</template>
<script>
export default {
data() {
return {
currentTime: '',
timer: null,
eventListeners: []
}
},
mounted() {
// 添加事件监听器
this.addEventListeners()
},
beforeDestroy() {
// 清理定时器
this.stopTimer()
// 移除事件监听器
this.removeEventListeners()
// 清理其他资源
this.cleanup()
},
methods: {
startTimer() {
this.stopTimer() // 先清理之前的定时器
this.timer = setInterval(() => {
this.currentTime = new Date().toLocaleTimeString()
}, 1000)
},
stopTimer() {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
},
addEventListeners() {
const networkListener = (res) => {
console.log('网络状态变化:', res.networkType)
}
uni.onNetworkStatusChange(networkListener)
this.eventListeners.push({
type: 'network',
listener: networkListener
})
},
removeEventListeners() {
this.eventListeners.forEach(({ type, listener }) => {
if (type === 'network') {
uni.offNetworkStatusChange(listener)
}
})
this.eventListeners = []
},
cleanup() {
// 清理大对象引用
this.largeData = null
// 清理闭包引用
this.callbacks = null
}
}
}
</script>
6. 性能监控
性能指标收集
javascript
// utils/performance-monitor.js
class PerformanceMonitor {
constructor() {
this.metrics = {}
this.startTimes = {}
}
// 开始计时
start(name) {
this.startTimes[name] = Date.now()
}
// 结束计时
end(name) {
if (this.startTimes[name]) {
const duration = Date.now() - this.startTimes[name]
this.metrics[name] = duration
delete this.startTimes[name]
console.log(`\${name}: \${duration}ms`)
return duration
}
}
// 记录页面加载时间
recordPageLoad(pageName) {
const loadTime = Date.now() - this.pageStartTime
this.metrics[`\${pageName}_load`] = loadTime
// 上报性能数据
this.reportMetrics({
page: pageName,
loadTime: loadTime,
timestamp: Date.now()
})
}
// 上报性能数据
reportMetrics(data) {
// 发送到分析服务
uni.request({
url: '/api/analytics/performance',
method: 'POST',
data: data
}).catch(err => {
console.error('性能数据上报失败:', err)
})
}
// 获取内存使用情况
getMemoryUsage() {
if (typeof performance !== 'undefined' && performance.memory) {
return {
used: performance.memory.usedJSHeapSize,
total: performance.memory.totalJSHeapSize,
limit: performance.memory.jsHeapSizeLimit
}
}
return null
}
}
export default new PerformanceMonitor()
小结
今天我们学习了:
- ✅ 代码层面的性能优化技巧
- ✅ 资源优化和懒加载策略
- ✅ 渲染优化和虚拟列表
- ✅ 网络请求优化
- ✅ 内存管理和泄漏防范
- ✅ 性能监控和指标收集
性能优化要点:
- 优化要有针对性,先测量再优化
- 避免过早优化,关注关键路径
- 平衡性能和代码可读性
- 持续监控和改进
下一篇预告
下一篇我们将开始实战项目的中篇《实战项目:待办事项小程序(中)- 功能实现和数据处理》,将前面学到的知识应用到实际项目中。
性能优化是一个持续的过程,每一个细节的优化都能带来用户体验的提升!