Skip to content

小程序性能优化指南

小程序性能优化是提升用户体验的关键环节。本指南将从启动性能、运行时性能、内存管理、网络优化等多个维度,为你提供全面的小程序性能优化策略和实践方案。

性能优化概述

性能指标

启动性能指标

  • 首屏渲染时间:从小程序启动到首屏内容完全显示的时间
  • 页面切换时间:页面间跳转的响应时间
  • 代码包下载时间:小程序代码包的下载和解析时间

运行时性能指标

  • 页面滚动流畅度:滚动时的帧率表现
  • 交互响应时间:用户操作到界面响应的延迟
  • 内存使用量:小程序运行时的内存占用

网络性能指标

  • 接口响应时间:API 请求的平均响应时间
  • 资源加载时间:图片、音频等资源的加载速度
  • 网络错误率:网络请求的失败比例

性能监控

使用小程序性能监控

javascript
// app.js
App({
  onLaunch() {
    // 启用性能监控
    if (wx.canIUse('getPerformance')) {
      const performance = wx.getPerformance()
      
      // 监听性能数据
      performance.createObserver((entryList) => {
        console.log('性能数据:', entryList.getEntries())
      }).observe({ entryTypes: ['render', 'script', 'loadPackage'] })
    }
  },
  
  onShow() {
    // 记录应用显示时间
    console.log('应用显示时间:', Date.now())
  }
})

自定义性能监控

javascript
// utils/performance.js
class PerformanceMonitor {
  constructor() {
    this.marks = new Map()
    this.measures = new Map()
  }
  
  // 标记时间点
  mark(name) {
    this.marks.set(name, Date.now())
  }
  
  // 测量时间间隔
  measure(name, startMark, endMark) {
    const startTime = this.marks.get(startMark)
    const endTime = this.marks.get(endMark) || Date.now()
    
    if (startTime) {
      const duration = endTime - startTime
      this.measures.set(name, duration)
      console.log(`[PERF] ${name}: ${duration}ms`)
      return duration
    }
  }
  
  // 获取所有测量结果
  getMeasures() {
    return Object.fromEntries(this.measures)
  }
  
  // 清除所有标记
  clear() {
    this.marks.clear()
    this.measures.clear()
  }
}

// 全局性能监控实例
const performanceMonitor = new PerformanceMonitor()

export default performanceMonitor

启动性能优化

代码包优化

代码分包

javascript
// app.json
{
  "pages": [
    "pages/index/index",
    "pages/profile/profile"
  ],
  "subPackages": [
    {
      "root": "pages/shop",
      "name": "shop",
      "pages": [
        "list/list",
        "detail/detail"
      ]
    },
    {
      "root": "pages/user",
      "name": "user",
      "pages": [
        "settings/settings",
        "orders/orders"
      ]
    }
  ],
  "preloadRule": {
    "pages/index/index": {
      "network": "all",
      "packages": ["shop"]
    }
  }
}

独立分包

javascript
// app.json
{
  "subPackages": [
    {
      "root": "pages/activity",
      "name": "activity",
      "pages": [
        "detail/detail"
      ],
      "independent": true
    }
  ]
}

代码压缩和混淆

javascript
// project.config.json
{
  "setting": {
    "minified": true,
    "minifyJS": true,
    "minifyWXML": true,
    "minifyWXSS": true,
    "codeProtect": true
  }
}

资源优化

图片优化

javascript
// utils/image.js
class ImageOptimizer {
  // 图片压缩
  static compressImage(src, quality = 0.8) {
    return new Promise((resolve, reject) => {
      wx.compressImage({
        src: src,
        quality: quality,
        success: resolve,
        fail: reject
      })
    })
  }
  
  // 选择合适的图片格式
  static chooseImageFormat(src) {
    const canvas = wx.createCanvasContext('imageCanvas')
    
    return new Promise((resolve) => {
      wx.getImageInfo({
        src: src,
        success: (res) => {
          const { width, height } = res
          
          // 根据图片特征选择格式
          if (width * height > 1000000) {
            // 大图使用 JPEG
            resolve('jpeg')
          } else if (src.includes('icon') || src.includes('logo')) {
            // 图标使用 PNG
            resolve('png')
          } else {
            // 默认使用 WebP(如果支持)
            resolve('webp')
          }
        }
      })
    })
  }
  
  // 懒加载图片
  static lazyLoadImage(selector, options = {}) {
    const observer = wx.createIntersectionObserver()
    
    observer.relativeToViewport({ bottom: 100 })
    observer.observe(selector, (res) => {
      if (res.intersectionRatio > 0) {
        const img = res.target
        const dataSrc = img.dataset.src
        
        if (dataSrc) {
          img.src = dataSrc
          img.removeAttribute('data-src')
          observer.unobserve(selector)
        }
      }
    })
  }
}

export default ImageOptimizer

字体优化

css
/* 使用系统字体 */
.text {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
}

/* 字体子集化 */
@font-face {
  font-family: 'CustomFont';
  src: url('fonts/custom-subset.woff2') format('woff2');
  font-display: swap;
  unicode-range: U+4E00-9FFF; /* 只包含中文字符 */
}

首屏优化

关键路径优化

javascript
// pages/index/index.js
Page({
  data: {
    // 首屏必需数据
    banners: [],
    categories: [],
    // 非首屏数据延迟加载
    recommendations: null,
    activities: null
  },
  
  onLoad() {
    // 优先加载首屏数据
    this.loadCriticalData()
    
    // 延迟加载非关键数据
    setTimeout(() => {
      this.loadNonCriticalData()
    }, 100)
  },
  
  async loadCriticalData() {
    try {
      const [banners, categories] = await Promise.all([
        this.getBanners(),
        this.getCategories()
      ])
      
      this.setData({
        banners,
        categories
      })
    } catch (error) {
      console.error('加载首屏数据失败:', error)
    }
  },
  
  async loadNonCriticalData() {
    try {
      const [recommendations, activities] = await Promise.all([
        this.getRecommendations(),
        this.getActivities()
      ])
      
      this.setData({
        recommendations,
        activities
      })
    } catch (error) {
      console.error('加载非关键数据失败:', error)
    }
  }
})

骨架屏

xml
<!-- pages/index/index.wxml -->
<view class="container">
  <!-- 骨架屏 -->
  <view wx:if="{{!dataLoaded}}" class="skeleton">
    <view class="skeleton-banner"></view>
    <view class="skeleton-categories">
      <view class="skeleton-category" wx:for="{{4}}" wx:key="index"></view>
    </view>
    <view class="skeleton-list">
      <view class="skeleton-item" wx:for="{{6}}" wx:key="index"></view>
    </view>
  </view>
  
  <!-- 实际内容 -->
  <view wx:else class="content">
    <swiper class="banner-swiper">
      <swiper-item wx:for="{{banners}}" wx:key="id">
        <image src="{{item.image}}" mode="aspectFill" />
      </swiper-item>
    </swiper>
    
    <view class="categories">
      <view class="category" wx:for="{{categories}}" wx:key="id">
        <image src="{{item.icon}}" />
        <text>{{item.name}}</text>
      </view>
    </view>
  </view>
</view>
css
/* pages/index/index.wxss */
.skeleton {
  padding: 20rpx;
}

.skeleton-banner {
  width: 100%;
  height: 300rpx;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
  border-radius: 10rpx;
  margin-bottom: 20rpx;
}

.skeleton-categories {
  display: flex;
  justify-content: space-between;
  margin-bottom: 30rpx;
}

.skeleton-category {
  width: 120rpx;
  height: 120rpx;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
  border-radius: 10rpx;
}

.skeleton-item {
  width: 100%;
  height: 200rpx;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
  border-radius: 10rpx;
  margin-bottom: 20rpx;
}

@keyframes loading {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -200% 0;
  }
}

运行时性能优化

渲染性能优化

减少 setData 调用

javascript
// 错误示例:频繁调用 setData
Page({
  updateList() {
    this.data.list.forEach((item, index) => {
      this.setData({
        [`list[${index}].status`]: 'updated'
      })
    })
  }
})

// 正确示例:批量更新
Page({
  updateList() {
    const updates = {}
    this.data.list.forEach((item, index) => {
      updates[`list[${index}].status`] = 'updated'
    })
    this.setData(updates)
  }
})

优化数据结构

javascript
// 错误示例:深层嵌套
Page({
  data: {
    user: {
      profile: {
        personal: {
          name: '张三',
          age: 25
        }
      }
    }
  },
  
  updateName(newName) {
    this.setData({
      'user.profile.personal.name': newName
    })
  }
})

// 正确示例:扁平化数据
Page({
  data: {
    userName: '张三',
    userAge: 25
  },
  
  updateName(newName) {
    this.setData({
      userName: newName
    })
  }
})

虚拟列表

javascript
// components/virtual-list/virtual-list.js
Component({
  properties: {
    items: {
      type: Array,
      value: []
    },
    itemHeight: {
      type: Number,
      value: 100
    },
    containerHeight: {
      type: Number,
      value: 600
    }
  },
  
  data: {
    visibleItems: [],
    scrollTop: 0,
    startIndex: 0,
    endIndex: 0
  },
  
  lifetimes: {
    attached() {
      this.updateVisibleItems()
    }
  },
  
  observers: {
    'items, containerHeight, itemHeight'() {
      this.updateVisibleItems()
    }
  },
  
  methods: {
    updateVisibleItems() {
      const { items, itemHeight, containerHeight } = this.properties
      const { scrollTop } = this.data
      
      const visibleCount = Math.ceil(containerHeight / itemHeight)
      const startIndex = Math.floor(scrollTop / itemHeight)
      const endIndex = Math.min(startIndex + visibleCount + 1, items.length)
      
      const visibleItems = items.slice(startIndex, endIndex).map((item, index) => ({
        ...item,
        index: startIndex + index,
        top: (startIndex + index) * itemHeight
      }))
      
      this.setData({
        visibleItems,
        startIndex,
        endIndex
      })
    },
    
    onScroll(e) {
      const scrollTop = e.detail.scrollTop
      this.setData({ scrollTop })
      
      // 节流更新
      clearTimeout(this.scrollTimer)
      this.scrollTimer = setTimeout(() => {
        this.updateVisibleItems()
      }, 16)
    }
  }
})
xml
<!-- components/virtual-list/virtual-list.wxml -->
<scroll-view 
  class="virtual-list" 
  scroll-y 
  style="height: {{containerHeight}}px"
  bindscroll="onScroll"
  scroll-top="{{scrollTop}}"
>
  <view 
    class="virtual-container" 
    style="height: {{items.length * itemHeight}}px"
  >
    <view 
      class="virtual-item" 
      wx:for="{{visibleItems}}" 
      wx:key="index"
      style="position: absolute; top: {{item.top}}px; height: {{itemHeight}}px"
    >
      <slot name="item" item="{{item}}"></slot>
    </view>
  </view>
</scroll-view>

事件优化

事件委托

javascript
// 错误示例:为每个项目绑定事件
Page({
  data: {
    list: [
      { id: 1, name: '项目1' },
      { id: 2, name: '项目2' }
    ]
  },
  
  onItemTap(e) {
    const id = e.currentTarget.dataset.id
    console.log('点击项目:', id)
  }
})
xml
<!-- 错误示例 -->
<view wx:for="{{list}}" wx:key="id" bindtap="onItemTap" data-id="{{item.id}}">
  {{item.name}}
</view>
javascript
// 正确示例:事件委托
Page({
  data: {
    list: [
      { id: 1, name: '项目1' },
      { id: 2, name: '项目2' }
    ]
  },
  
  onListTap(e) {
    const id = e.target.dataset.id
    if (id) {
      console.log('点击项目:', id)
    }
  }
})
xml
<!-- 正确示例 -->
<view class="list" bindtap="onListTap">
  <view wx:for="{{list}}" wx:key="id" class="item" data-id="{{item.id}}">
    {{item.name}}
  </view>
</view>

防抖和节流

javascript
// utils/throttle.js
export function throttle(func, delay) {
  let timer = null
  return function(...args) {
    if (!timer) {
      timer = setTimeout(() => {
        func.apply(this, args)
        timer = null
      }, delay)
    }
  }
}

export function debounce(func, delay) {
  let timer = null
  return function(...args) {
    clearTimeout(timer)
    timer = setTimeout(() => {
      func.apply(this, args)
    }, delay)
  }
}

// 使用示例
import { throttle, debounce } from '../../utils/throttle'

Page({
  onLoad() {
    // 节流滚动事件
    this.onScrollThrottled = throttle(this.onScroll.bind(this), 100)
    // 防抖搜索
    this.onSearchDebounced = debounce(this.onSearch.bind(this), 300)
  },
  
  onScroll(e) {
    console.log('滚动位置:', e.detail.scrollTop)
  },
  
  onSearch(keyword) {
    console.log('搜索关键词:', keyword)
    // 执行搜索逻辑
  }
})

内存管理优化

内存泄漏预防

清理定时器

javascript
Page({
  onLoad() {
    // 设置定时器
    this.timer = setInterval(() => {
      this.updateTime()
    }, 1000)
  },
  
  onUnload() {
    // 清理定时器
    if (this.timer) {
      clearInterval(this.timer)
      this.timer = null
    }
  },
  
  updateTime() {
    this.setData({
      currentTime: new Date().toLocaleTimeString()
    })
  }
})

清理事件监听

javascript
Page({
  onLoad() {
    // 监听网络状态变化
    this.onNetworkStatusChange = this.handleNetworkStatusChange.bind(this)
    wx.onNetworkStatusChange(this.onNetworkStatusChange)
  },
  
  onUnload() {
    // 移除事件监听
    if (this.onNetworkStatusChange) {
      wx.offNetworkStatusChange(this.onNetworkStatusChange)
      this.onNetworkStatusChange = null
    }
  },
  
  handleNetworkStatusChange(res) {
    console.log('网络状态变化:', res.isConnected)
  }
})

图片资源管理

javascript
// utils/image-cache.js
class ImageCache {
  constructor(maxSize = 50) {
    this.cache = new Map()
    this.maxSize = maxSize
  }
  
  set(key, value) {
    if (this.cache.size >= this.maxSize) {
      // 删除最旧的缓存
      const firstKey = this.cache.keys().next().value
      this.cache.delete(firstKey)
    }
    this.cache.set(key, value)
  }
  
  get(key) {
    return this.cache.get(key)
  }
  
  clear() {
    this.cache.clear()
  }
  
  // 预加载图片
  preloadImage(src) {
    return new Promise((resolve, reject) => {
      if (this.cache.has(src)) {
        resolve(this.cache.get(src))
        return
      }
      
      wx.getImageInfo({
        src: src,
        success: (res) => {
          this.set(src, res)
          resolve(res)
        },
        fail: reject
      })
    })
  }
}

const imageCache = new ImageCache()
export default imageCache

数据管理优化

分页加载

javascript
Page({
  data: {
    list: [],
    page: 1,
    pageSize: 20,
    hasMore: true,
    loading: false
  },
  
  onLoad() {
    this.loadData()
  },
  
  onReachBottom() {
    if (this.data.hasMore && !this.data.loading) {
      this.loadMore()
    }
  },
  
  async loadData() {
    this.setData({ loading: true })
    
    try {
      const res = await this.fetchData(1, this.data.pageSize)
      this.setData({
        list: res.data,
        page: 1,
        hasMore: res.data.length === this.data.pageSize,
        loading: false
      })
    } catch (error) {
      console.error('加载数据失败:', error)
      this.setData({ loading: false })
    }
  },
  
  async loadMore() {
    this.setData({ loading: true })
    
    try {
      const nextPage = this.data.page + 1
      const res = await this.fetchData(nextPage, this.data.pageSize)
      
      this.setData({
        list: [...this.data.list, ...res.data],
        page: nextPage,
        hasMore: res.data.length === this.data.pageSize,
        loading: false
      })
    } catch (error) {
      console.error('加载更多失败:', error)
      this.setData({ loading: false })
    }
  },
  
  async fetchData(page, pageSize) {
    return new Promise((resolve) => {
      wx.request({
        url: '/api/data',
        data: { page, pageSize },
        success: resolve
      })
    })
  }
})

数据缓存策略

javascript
// utils/cache.js
class DataCache {
  constructor() {
    this.cache = new Map()
    this.expireTime = new Map()
  }
  
  set(key, data, ttl = 300000) { // 默认5分钟过期
    this.cache.set(key, data)
    this.expireTime.set(key, Date.now() + ttl)
  }
  
  get(key) {
    const expireTime = this.expireTime.get(key)
    if (expireTime && Date.now() > expireTime) {
      this.delete(key)
      return null
    }
    return this.cache.get(key)
  }
  
  delete(key) {
    this.cache.delete(key)
    this.expireTime.delete(key)
  }
  
  clear() {
    this.cache.clear()
    this.expireTime.clear()
  }
  
  // 清理过期缓存
  cleanup() {
    const now = Date.now()
    for (const [key, expireTime] of this.expireTime.entries()) {
      if (now > expireTime) {
        this.delete(key)
      }
    }
  }
}

const dataCache = new DataCache()

// 定期清理过期缓存
setInterval(() => {
  dataCache.cleanup()
}, 60000) // 每分钟清理一次

export default dataCache

网络优化

请求优化

请求合并

javascript
// utils/request-batch.js
class RequestBatch {
  constructor() {
    this.queue = []
    this.timer = null
  }
  
  add(request) {
    return new Promise((resolve, reject) => {
      this.queue.push({
        ...request,
        resolve,
        reject
      })
      
      this.scheduleFlush()
    })
  }
  
  scheduleFlush() {
    if (this.timer) return
    
    this.timer = setTimeout(() => {
      this.flush()
    }, 10) // 10ms 内的请求合并
  }
  
  flush() {
    if (this.queue.length === 0) return
    
    const requests = this.queue.splice(0)
    this.timer = null
    
    // 按接口分组
    const groups = new Map()
    requests.forEach(req => {
      const key = `${req.method || 'GET'}_${req.url}`
      if (!groups.has(key)) {
        groups.set(key, [])
      }
      groups.get(key).push(req)
    })
    
    // 批量请求
    groups.forEach((reqs, key) => {
      if (reqs.length === 1) {
        // 单个请求直接发送
        this.sendRequest(reqs[0])
      } else {
        // 多个请求合并
        this.sendBatchRequest(reqs)
      }
    })
  }
  
  sendRequest(req) {
    wx.request({
      ...req,
      success: req.resolve,
      fail: req.reject
    })
  }
  
  sendBatchRequest(requests) {
    const batchData = requests.map(req => ({
      url: req.url,
      method: req.method || 'GET',
      data: req.data
    }))
    
    wx.request({
      url: '/api/batch',
      method: 'POST',
      data: { requests: batchData },
      success: (res) => {
        res.data.results.forEach((result, index) => {
          requests[index].resolve(result)
        })
      },
      fail: (error) => {
        requests.forEach(req => req.reject(error))
      }
    })
  }
}

const requestBatch = new RequestBatch()
export default requestBatch

请求缓存

javascript
// utils/request-cache.js
import dataCache from './cache'

class RequestCache {
  constructor() {
    this.pendingRequests = new Map()
  }
  
  async request(options) {
    const cacheKey = this.getCacheKey(options)
    
    // 检查缓存
    const cached = dataCache.get(cacheKey)
    if (cached) {
      return cached
    }
    
    // 检查是否有相同的请求正在进行
    if (this.pendingRequests.has(cacheKey)) {
      return this.pendingRequests.get(cacheKey)
    }
    
    // 发送请求
    const promise = this.sendRequest(options)
    this.pendingRequests.set(cacheKey, promise)
    
    try {
      const result = await promise
      
      // 缓存结果
      if (options.cache !== false) {
        dataCache.set(cacheKey, result, options.cacheTTL)
      }
      
      return result
    } finally {
      this.pendingRequests.delete(cacheKey)
    }
  }
  
  sendRequest(options) {
    return new Promise((resolve, reject) => {
      wx.request({
        ...options,
        success: resolve,
        fail: reject
      })
    })
  }
  
  getCacheKey(options) {
    const { url, method = 'GET', data } = options
    return `${method}_${url}_${JSON.stringify(data || {})}`
  }
}

const requestCache = new RequestCache()
export default requestCache

资源预加载

关键资源预加载

javascript
// utils/preloader.js
class ResourcePreloader {
  constructor() {
    this.preloadedImages = new Set()
    this.preloadedData = new Map()
  }
  
  // 预加载图片
  async preloadImages(urls) {
    const promises = urls.map(url => {
      if (this.preloadedImages.has(url)) {
        return Promise.resolve()
      }
      
      return new Promise((resolve, reject) => {
        wx.getImageInfo({
          src: url,
          success: () => {
            this.preloadedImages.add(url)
            resolve()
          },
          fail: reject
        })
      })
    })
    
    try {
      await Promise.all(promises)
      console.log('图片预加载完成')
    } catch (error) {
      console.error('图片预加载失败:', error)
    }
  }
  
  // 预加载数据
  async preloadData(requests) {
    const promises = requests.map(async (req) => {
      try {
        const result = await this.request(req)
        this.preloadedData.set(req.key, result)
      } catch (error) {
        console.error(`预加载数据失败 ${req.key}:`, error)
      }
    })
    
    await Promise.all(promises)
    console.log('数据预加载完成')
  }
  
  // 获取预加载的数据
  getPreloadedData(key) {
    return this.preloadedData.get(key)
  }
  
  request(options) {
    return new Promise((resolve, reject) => {
      wx.request({
        ...options,
        success: resolve,
        fail: reject
      })
    })
  }
}

const preloader = new ResourcePreloader()
export default preloader

智能预加载

javascript
// utils/smart-preloader.js
class SmartPreloader {
  constructor() {
    this.userBehavior = new Map()
    this.preloadRules = new Map()
  }
  
  // 记录用户行为
  recordBehavior(page, action, target) {
    const key = `${page}_${action}`
    if (!this.userBehavior.has(key)) {
      this.userBehavior.set(key, new Map())
    }
    
    const actionMap = this.userBehavior.get(key)
    const count = actionMap.get(target) || 0
    actionMap.set(target, count + 1)
    
    // 更新预加载规则
    this.updatePreloadRules(key, target, count + 1)
  }
  
  // 更新预加载规则
  updatePreloadRules(key, target, count) {
    if (count >= 3) { // 用户访问3次以上的资源进行预加载
      if (!this.preloadRules.has(key)) {
        this.preloadRules.set(key, new Set())
      }
      this.preloadRules.get(key).add(target)
    }
  }
  
  // 获取预加载建议
  getPreloadSuggestions(page, action) {
    const key = `${page}_${action}`
    return this.preloadRules.get(key) || new Set()
  }
  
  // 执行智能预加载
  async executeSmartPreload(page, action) {
    const suggestions = this.getPreloadSuggestions(page, action)
    
    for (const target of suggestions) {
      try {
        if (target.startsWith('http')) {
          // 预加载图片
          await wx.getImageInfo({ src: target })
        } else {
          // 预加载页面数据
          await this.preloadPageData(target)
        }
      } catch (error) {
        console.error(`智能预加载失败 ${target}:`, error)
      }
    }
  }
  
  async preloadPageData(page) {
    // 根据页面预加载对应数据
    const dataLoaders = {
      'product-detail': () => this.preloadProductData(),
      'user-profile': () => this.preloadUserData(),
      'order-list': () => this.preloadOrderData()
    }
    
    const loader = dataLoaders[page]
    if (loader) {
      await loader()
    }
  }
}

const smartPreloader = new SmartPreloader()
export default smartPreloader

样式和动画优化

CSS 优化

减少重排和重绘

css
/* 错误示例:会触发重排 */
.bad-animation {
  transition: width 0.3s, height 0.3s, left 0.3s, top 0.3s;
}

/* 正确示例:只触发合成 */
.good-animation {
  transition: transform 0.3s, opacity 0.3s;
}

/* 使用 transform 替代位置属性 */
.move-element {
  transform: translateX(100px) translateY(50px);
}

/* 使用 opacity 替代 visibility */
.fade-element {
  opacity: 0;
}

优化选择器

css
/* 错误示例:复杂选择器 */
.container .list .item .title .text {
  color: #333;
}

/* 正确示例:简化选择器 */
.item-title-text {
  color: #333;
}

/* 使用类选择器替代标签选择器 */
.button {
  background: #007bff;
}

/* 避免通配符选择器 */
.container * {
  box-sizing: border-box; /* 避免这样使用 */
}

动画优化

使用 CSS 动画替代 JS 动画

css
/* CSS 动画 */
@keyframes slideIn {
  from {
    transform: translateX(-100%);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

.slide-in {
  animation: slideIn 0.3s ease-out;
}

/* 硬件加速 */
.hardware-accelerated {
  transform: translateZ(0);
  will-change: transform;
}

动画性能监控

javascript
// utils/animation-monitor.js
class AnimationMonitor {
  constructor() {
    this.animations = new Map()
    this.performanceData = []
  }
  
  // 开始监控动画
  startMonitoring(name) {
    const startTime = performance.now()
    this.animations.set(name, {
      startTime,
      frames: 0,
      droppedFrames: 0
    })
    
    this.monitorFrames(name)
  }
  
  // 监控帧率
  monitorFrames(name) {
    const animation = this.animations.get(name)
    if (!animation) return
    
    const checkFrame = () => {
      if (this.animations.has(name)) {
        animation.frames++
        
        // 检查是否掉帧
        const expectedFrames = (performance.now() - animation.startTime) / 16.67
        if (animation.frames < expectedFrames * 0.9) {
          animation.droppedFrames++
        }
        
        requestAnimationFrame(checkFrame)
      }
    }
    
    requestAnimationFrame(checkFrame)
  }
  
  // 结束监控
  endMonitoring(name) {
    const animation = this.animations.get(name)
    if (!animation) return
    
    const duration = performance.now() - animation.startTime
    const fps = (animation.frames / duration) * 1000
    const dropRate = animation.droppedFrames / animation.frames
    
    const data = {
      name,
      duration,
      fps,
      dropRate,
      frames: animation.frames,
      droppedFrames: animation.droppedFrames
    }
    
    this.performanceData.push(data)
    this.animations.delete(name)
    
    console.log(`动画性能 ${name}:`, data)
    return data
  }
  
  // 获取性能报告
  getPerformanceReport() {
    return {
      totalAnimations: this.performanceData.length,
      averageFPS: this.performanceData.reduce((sum, data) => sum + data.fps, 0) / this.performanceData.length,
      averageDropRate: this.performanceData.reduce((sum, data) => sum + data.dropRate, 0) / this.performanceData.length,
      animations: this.performanceData
    }
  }
}

const animationMonitor = new AnimationMonitor()
export default animationMonitor

工具和调试

性能分析工具

自定义性能分析器

javascript
// utils/profiler.js
class Profiler {
  constructor() {
    this.profiles = new Map()
    this.isEnabled = true
  }
  
  // 开始性能分析
  start(name) {
    if (!this.isEnabled) return
    
    this.profiles.set(name, {
      startTime: performance.now(),
      startMemory: this.getMemoryUsage(),
      marks: []
    })
  }
  
  // 添加标记点
  mark(profileName, markName) {
    if (!this.isEnabled) return
    
    const profile = this.profiles.get(profileName)
    if (profile) {
      profile.marks.push({
        name: markName,
        time: performance.now() - profile.startTime,
        memory: this.getMemoryUsage()
      })
    }
  }
  
  // 结束性能分析
  end(name) {
    if (!this.isEnabled) return
    
    const profile = this.profiles.get(name)
    if (!profile) return
    
    const endTime = performance.now()
    const endMemory = this.getMemoryUsage()
    
    const result = {
      name,
      duration: endTime - profile.startTime,
      memoryDelta: endMemory - profile.startMemory,
      marks: profile.marks,
      timestamp: new Date().toISOString()
    }
    
    this.profiles.delete(name)
    this.logResult(result)
    
    return result
  }
  
  // 获取内存使用情况
  getMemoryUsage() {
    if (wx.getPerformance && wx.getPerformance().memory) {
      return wx.getPerformance().memory.usedJSHeapSize
    }
    return 0
  }
  
  // 记录结果
  logResult(result) {
    console.group(`性能分析: ${result.name}`)
    console.log(`总耗时: ${result.duration.toFixed(2)}ms`)
    console.log(`内存变化: ${(result.memoryDelta / 1024 / 1024).toFixed(2)}MB`)
    
    if (result.marks.length > 0) {
      console.log('标记点:')
      result.marks.forEach(mark => {
        console.log(`  ${mark.name}: ${mark.time.toFixed(2)}ms`)
      })
    }
    
    console.groupEnd()
  }
  
  // 启用/禁用分析器
  setEnabled(enabled) {
    this.isEnabled = enabled
  }
}

const profiler = new Profiler()
export default profiler

使用示例

javascript
// pages/product/detail.js
import profiler from '../../utils/profiler'

Page({
  onLoad(options) {
    profiler.start('page-load')
    
    this.loadProductData(options.id)
  },
  
  async loadProductData(id) {
    profiler.mark('page-load', 'start-api-request')
    
    try {
      const product = await this.fetchProduct(id)
      profiler.mark('page-load', 'api-request-complete')
      
      this.setData({ product })
      profiler.mark('page-load', 'data-set-complete')
      
      await this.loadRelatedProducts(product.categoryId)
      profiler.mark('page-load', 'related-products-loaded')
      
    } catch (error) {
      console.error('加载产品数据失败:', error)
    } finally {
      profiler.end('page-load')
    }
  }
})

调试工具

性能监控面板

javascript
// components/performance-panel/performance-panel.js
Component({
  data: {
    visible: false,
    fps: 0,
    memory: 0,
    networkRequests: 0,
    errors: []
  },
  
  lifetimes: {
    attached() {
      this.startMonitoring()
    },
    
    detached() {
      this.stopMonitoring()
    }
  },
  
  methods: {
    // 开始监控
    startMonitoring() {
      // 监控 FPS
      this.monitorFPS()
      
      // 监控内存
      this.monitorMemory()
      
      // 监控网络请求
      this.monitorNetwork()
      
      // 监控错误
      this.monitorErrors()
    },
    
    // 监控帧率
    monitorFPS() {
      let frames = 0
      let lastTime = performance.now()
      
      const countFPS = () => {
        frames++
        const currentTime = performance.now()
        
        if (currentTime - lastTime >= 1000) {
          this.setData({ fps: frames })
          frames = 0
          lastTime = currentTime
        }
        
        if (this.data.visible) {
          requestAnimationFrame(countFPS)
        }
      }
      
      requestAnimationFrame(countFPS)
    },
    
    // 监控内存
    monitorMemory() {
      const updateMemory = () => {
        if (wx.getPerformance && wx.getPerformance().memory) {
          const memory = wx.getPerformance().memory.usedJSHeapSize / 1024 / 1024
          this.setData({ memory: memory.toFixed(2) })
        }
        
        if (this.data.visible) {
          setTimeout(updateMemory, 1000)
        }
      }
      
      updateMemory()
    },
    
    // 监控网络请求
    monitorNetwork() {
      const originalRequest = wx.request
      let requestCount = 0
      
      wx.request = (options) => {
        requestCount++
        this.setData({ networkRequests: requestCount })
        
        return originalRequest.call(wx, options)
      }
    },
    
    // 监控错误
    monitorErrors() {
      const originalError = console.error
      const errors = []
      
      console.error = (...args) => {
        errors.push({
          message: args.join(' '),
          timestamp: new Date().toLocaleTimeString()
        })
        
        this.setData({ errors: errors.slice(-10) }) // 只保留最近10个错误
        
        return originalError.apply(console, args)
      }
    },
    
    // 切换面板显示
    toggle() {
      this.setData({ visible: !this.data.visible })
      
      if (this.data.visible) {
        this.startMonitoring()
      }
    },
    
    // 清除错误日志
    clearErrors() {
      this.setData({ errors: [] })
    }
  }
})
xml
<!-- components/performance-panel/performance-panel.wxml -->
<view class="performance-panel" wx:if="{{visible}}">
  <view class="panel-header">
    <text class="title">性能监控</text>
    <text class="close" bindtap="toggle">×</text>
  </view>
  
  <view class="metrics">
    <view class="metric">
      <text class="label">FPS:</text>
      <text class="value {{fps < 30 ? 'warning' : ''}}">{{fps}}</text>
    </view>
    
    <view class="metric">
      <text class="label">内存:</text>
      <text class="value {{memory > 50 ? 'warning' : ''}}">{{memory}}MB</text>
    </view>
    
    <view class="metric">
      <text class="label">网络请求:</text>
      <text class="value">{{networkRequests}}</text>
    </view>
  </view>
  
  <view class="errors" wx:if="{{errors.length > 0}}">
    <view class="errors-header">
      <text>错误日志</text>
      <text class="clear" bindtap="clearErrors">清除</text>
    </view>
    
    <scroll-view class="errors-list" scroll-y>
      <view class="error-item" wx:for="{{errors}}" wx:key="timestamp">
        <text class="error-time">{{item.timestamp}}</text>
        <text class="error-message">{{item.message}}</text>
      </view>
    </scroll-view>
  </view>
</view>

<!-- 悬浮按钮 -->
<view class="performance-toggle" wx:if="{{!visible}}" bindtap="toggle">
  <text>性能</text>
</view>

最佳实践总结

性能优化检查清单

启动性能

  • [ ] 代码包大小控制在 2MB 以内
  • [ ] 使用分包加载非关键功能
  • [ ] 首屏数据优先加载
  • [ ] 实现骨架屏提升感知性能
  • [ ] 关键资源预加载

运行时性能

  • [ ] 减少 setData 调用频率
  • [ ] 优化数据结构,避免深层嵌套
  • [ ] 使用虚拟列表处理长列表
  • [ ] 实现事件委托减少事件绑定
  • [ ] 添加防抖节流优化高频事件

内存管理

  • [ ] 及时清理定时器和事件监听
  • [ ] 实现图片缓存和懒加载
  • [ ] 使用分页加载控制数据量
  • [ ] 定期清理过期缓存

网络优化

  • [ ] 实现请求缓存和去重
  • [ ] 合并相似请求减少网络开销
  • [ ] 关键资源预加载
  • [ ] 智能预加载基于用户行为

样式和动画

  • [ ] 使用 transform 和 opacity 实现动画
  • [ ] 避免复杂 CSS 选择器
  • [ ] 启用硬件加速
  • [ ] 监控动画性能

性能监控指标

关键指标

  • 首屏时间 < 1.5s
  • 页面切换时间 < 300ms
  • 滚动帧率 > 50fps
  • 内存使用 < 100MB
  • 网络请求成功率 > 99%

监控实现

javascript
// utils/performance-metrics.js
class PerformanceMetrics {
  constructor() {
    this.metrics = {
      firstScreenTime: 0,
      pageTransitionTime: 0,
      scrollFPS: 0,
      memoryUsage: 0,
      networkSuccessRate: 0
    }
  }
  
  // 记录首屏时间
  recordFirstScreenTime(time) {
    this.metrics.firstScreenTime = time
    this.reportMetric('first_screen_time', time)
  }
  
  // 记录页面切换时间
  recordPageTransitionTime(time) {
    this.metrics.pageTransitionTime = time
    this.reportMetric('page_transition_time', time)
  }
  
  // 上报性能指标
  reportMetric(name, value) {
    // 上报到性能监控平台
    wx.request({
      url: '/api/performance/report',
      method: 'POST',
      data: {
        metric: name,
        value: value,
        timestamp: Date.now(),
        userAgent: wx.getSystemInfoSync()
      }
    })
  }
  
  // 获取性能报告
  getReport() {
    return {
      ...this.metrics,
      timestamp: new Date().toISOString(),
      recommendations: this.getRecommendations()
    }
  }
  
  // 获取优化建议
  getRecommendations() {
    const recommendations = []
    
    if (this.metrics.firstScreenTime > 1500) {
      recommendations.push('首屏时间过长,建议优化关键路径')
    }
    
    if (this.metrics.pageTransitionTime > 300) {
      recommendations.push('页面切换时间过长,建议减少数据传输')
    }
    
    if (this.metrics.scrollFPS < 50) {
      recommendations.push('滚动性能不佳,建议优化渲染逻辑')
    }
    
    if (this.metrics.memoryUsage > 100) {
      recommendations.push('内存使用过高,建议检查内存泄漏')
    }
    
    return recommendations
  }
}

const performanceMetrics = new PerformanceMetrics()
export default performanceMetrics

扩展资源

官方文档

工具推荐

  • 微信开发者工具:内置性能分析面板
  • 小程序助手:性能监控和分析
  • Fundebug:错误监控和性能分析
  • Sentry:应用性能监控

学习资源

  • 微信小程序性能优化最佳实践
  • 前端性能优化技术分享
  • 小程序开发者社区讨论

通过本指南的学习和实践,你应该能够全面掌握小程序性能优化的各个方面,从启动性能到运行时性能,从内存管理到网络优化,系统性地提升小程序的用户体验。记住,性能优化是一个持续的过程,需要结合实际业务场景和用户反馈不断调整和改进。

连接多端,赋能创新