Skip to content

性能优化技巧 - 让你的小程序跑得更快

⚡ 性能是用户体验的基础,今天我们来学习如何让小程序跑得更快更流畅

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()

小结

今天我们学习了:

  • ✅ 代码层面的性能优化技巧
  • ✅ 资源优化和懒加载策略
  • ✅ 渲染优化和虚拟列表
  • ✅ 网络请求优化
  • ✅ 内存管理和泄漏防范
  • ✅ 性能监控和指标收集

性能优化要点

  • 优化要有针对性,先测量再优化
  • 避免过早优化,关注关键路径
  • 平衡性能和代码可读性
  • 持续监控和改进

下一篇预告

下一篇我们将开始实战项目的中篇《实战项目:待办事项小程序(中)- 功能实现和数据处理》,将前面学到的知识应用到实际项目中。


性能优化是一个持续的过程,每一个细节的优化都能带来用户体验的提升!