前端性能优化实战我用这几个技巧让页面加载速度提升60%
这次性能优化真的把我折腾惨了
最近接了个老项目重构的任务,说实话,接手的时候就知道会有不少坑。这个项目原本是个电商后台系统,用户量不算特别大,但响应速度慢得要命,特别是列表页面经常卡顿,用户体验很差。
项目初期做技术调研的时候,我就发现这个问题比想象中严重。Vue 2.x 版本,大量的 v-for 渲染,还有各种不必要的响应式数据监听。客户抱怨了好几个月,说页面打开要十几秒,表格滚动卡成PPT。我看了下代码,简直就是性能杀手集合体。
开始动手,发现比我想象的复杂
第一眼看到代码就想直接开干,把所有明显的性能问题修复一遍。结果开始动手才发现,这可不是简单的优化几个组件那么简单。
首先就是那个商品列表页面,用了 Vue 2.6.14,渲染几千条数据,而且每行都有复杂的交互功能。我测了一下,光是渲染就要5-8秒,滚动更是卡得不行。开始我以为是虚拟滚动的问题,后来发现连基础的数据绑定都存在问题。
原来的代码大概是这样:
// 原始代码,典型的性能杀手
export default {
data() {
return {
productList: [], // 几千条数据
selectedItems: []
}
},
computed: {
processedList() {
return this.productList.map(item => {
// 对每条数据进行复杂的计算
return {
...item,
formattedPrice: this.formatPrice(item.price),
statusText: this.getStatusText(item.status),
// 还有一堆复杂的处理
}
})
}
},
methods: {
formatPrice(price) {
// 复杂的格式化逻辑
return new Intl.NumberFormat().format(price)
}
}
}
第一个坑:大量不必要的响应式数据
最开始我犯了个错误,想当然地认为把所有数据都放在 data 里就好了。结果发现每次数据更新都会触发整个列表的重新渲染,特别是那个 computed 属性,每次稍微改动一个数据,整个列表就重新计算一遍。
后来调整了方案,把不需要响应式的静态数据移到了组件外部:
// 将不需要响应的数据移出data
const staticConfig = {
currency: 'CNY',
decimals: 2
}
export default {
data() {
return {
productList: [],
selectedIds: new Set()
}
},
created() {
// 非响应式数据存储
this.staticConfig = staticConfig
},
computed: {
processedList() {
return this.productList.map(item => {
return {
...item,
// 简化的计算逻辑
formattedPrice: this.getCachedFormattedPrice(item.id, item.price)
}
})
}
},
methods: {
getCachedFormattedPrice(id, price) {
if (!this.priceCache) {
this.priceCache = new Map()
}
const cacheKey = ${id}-${price}
if (this.priceCache.has(cacheKey)) {
return this.priceCache.get(cacheKey)
}
const formatted = new Intl.NumberFormat().format(price)
this.priceCache.set(cacheKey, formatted)
return formatted
}
}
}
第二个坑:虚拟滚动的兼容性问题
处理完响应式数据的问题,接下来就是虚拟滚动了。原来的想法很简单,引入一个虚拟滚动组件,搞定渲染性能问题。但是折腾半天发现,业务逻辑太复杂,很多交互功能在虚拟滚动下都不工作了。
原来的表格有很多嵌套的组件,每行都有编辑按钮、下拉选择框等等。用虚拟滚动之后,这些动态组件的状态管理变得很麻烦。开始没想到这点,后来调整了好几版才勉强跑起来。
<!-- 虚拟滚动组件实现 -->
<template>
<div class="virtual-list" @scroll="handleScroll">
<div :style="{ height: totalHeight + 'px' }" class="scroll-area">
<div
:style="{ transform: translateY(${offsetTop}px) }"
class="visible-items"
>
<div
v-for="item in visibleItems"
:key="item.id"
class="list-item"
>
<product-row
:product="item"
@edit="handleEdit"
@select="toggleSelection"
/>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'VirtualProductList',
props: {
items: Array,
itemHeight: {
type: Number,
default: 80
}
},
data() {
return {
scrollTop: 0,
containerHeight: 400
}
},
computed: {
totalHeight() {
return this.items.length * this.itemHeight
},
startIndex() {
return Math.floor(this.scrollTop / this.itemHeight)
},
endIndex() {
const maxVisible = Math.ceil(this.containerHeight / this.itemHeight)
return Math.min(this.startIndex + maxVisible + 5, this.items.length)
},
visibleItems() {
return this.items.slice(this.startIndex, this.endIndex)
},
offsetTop() {
return this.startIndex * this.itemHeight
}
},
methods: {
handleScroll(e) {
this.scrollTop = e.target.scrollTop
}
}
}
</script>
第三个坑:缓存策略的权衡
缓存这部分确实让我纠结了很久。既要保证数据的实时性,又要避免重复计算,还要考虑内存占用。开始搞了一个复杂的缓存机制,结果发现缓存命中率不高,反而增加了复杂度。
后来简化了方案,只对一些计算成本高的数据做缓存:
methods: {
initPerformanceOptimizations() {
// 数据分页加载
this.loadDataInChunks()
// 计算属性缓存
this.setupComputedCache()
// 防抖处理
this.debouncedUpdate = this.debounce(this.updateView, 16)
},
loadDataInChunks() {
const chunkSize = 100
let index = 0
const loadChunk = () => {
const chunk = this.rawData.slice(index, index + chunkSize)
this.processedData.push(...chunk)
index += chunkSize
if (index < this.rawData.length) {
requestAnimationFrame(loadChunk)
}
}
loadChunk()
},
debounce(func, wait) {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
}
最终效果还行,但不是完美
经过这些调整,页面性能确实提升了不少。渲染时间从原来的5-8秒降低到了1-2秒,滚动也基本流畅了。不过说实话,还有一些细节问题没完全解决。
比如在低端设备上偶尔还是会有卡顿,特别是在快速滚动的时候。还有内存占用虽然优化了一些,但对于超大数据集来说,还是会占用不少内存。但是客户那边验收通过了,这些问题暂时也就先这样了。
整体来说,这次优化让我对Vue性能优化有了更深的认识。以前总觉得虚拟滚动就能解决一切问题,实际上还是要根据具体场景来调整。每个优化点都可能带来新的问题,需要反复测试和调整。
一点小结
性能优化真的是个细致活儿,每个项目的情况都不一样。这次主要是解决了响应式数据滥用和渲染性能的问题,但还有更多可以优化的地方,比如网络请求的并发控制、组件懒加载等等。
以上是我这次性能优化的一些经验分享,希望对你有帮助。有什么更好的方案也欢迎交流讨论。

暂无评论