最近在进行GIS开发时,遇到了一些与vue2搭配上的问题。特别是在使用Cesium进行3D地理信息系统的开发时,因为之前没有太多相关经验,所以主要是通过在网上查找资料和代码示例来学习(主要是Ctrl+C和Ctrl+V)。有一天,领导要求我们在水库的模型中加入动态水位的控制。
问题背景
领导的要求看似简单:在空的水库中加入水,并且水位能够动态变化。看上去只是一个普通的动画效果,但在实现过程中却遇到了意想不到的性能问题。
解决方案的初步设想
CreateReservoir(positions, waterHeight) {
this.Reservoir= viewer.entities.add({
polygon: {
hierarchy: new Cesium.PolygonHierarchy(positions),
extrudedHeight: waterHeight,
material: new Cesium.Color.fromBytes(64, 157, 253, 150),
perPositionHeight: true,
},
});
}
然后使用requestAnimationFrame变化到目标水位
性能问题
效果确实可以实现,也没什么问题,但是发现页面有点卡卡的感觉,因为刚开始这个水库周围都是比较平坦的,且暂时没有高程数据,就是围着这个水库画了一个多边形,想着可能标点有点多的问题。后来就想着使用还是感觉卡卡的,就使用了Primitive来创建水库水位,在进行逐步优化内部的一些方法。
//物体的创建
function useCreatePolygonGeometry(boundary, extrudedHeight) {
return new Cesium.PolygonGeometry({
polygonHierarchy: new Cesium.PolygonHierarchy(
Cesium.Cartesian3.fromDegreesArray(boundary)
),
extrudedHeight,
vertexFormat: Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT,
});
}
function useDrawRiver(boundary, extrudedHeight, waterColor) {
//初始高度
let riverHeight = extrudedHeight;
//创建集合体传入初始高度和边界点
const polygon = useCreatePolygonGeometry(boundary, riverHeight);
riverInstance = new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: polygon,
}),
appearance: new Cesium.EllipsoidSurfaceAppearance({
aboveGround: true,
}),
show: true,
asynchronous: false,
releaseGeometryInstances: false,
});
const riverMaterial = new Cesium.Material({
fabric: {
type: "Water",
uniforms: {
baseWaterColor: new Cesium.Color(64 / 255.0, 157 / 255.0, 253 / 255.0, 0.6), // 水的基本颜色
normalMap: require("@/assets/images/water.webp"), // 水的法线贴图
frequency:2000.0, // 水波纹的数量
animationSpeed: 0.05, // 水的流速
amplitude: 5, // 水波纹振幅
specularIntensity: 2, // 镜面反射强度
},
},
});
//设置集合体材质
riverInstance.appearance.material = riverMaterial;
const scene = viewer.scene;
scene.primitives.add(riverInstance);
//监听物体高度属性的修改
Object.defineProperty(riverInstance, "extrudedHeight", {
get() {
return riverHeight;
},
set(newVal) {
if (typeof newVal !== "number") {
return;
}
riverHeight = newVal;
riverInstance._state = 3; // 重置primitive状态触发cesium update方法
riverInstance._appearance = undefined;
riverInstance.geometryInstances.geometry = useCreatePolygonGeometry(
boundary,
riverHeight
);
},
});
return riverInstance;
}
//组件销毁前移除物体
function removeWaterRiver() {
if (riverInstance) {
viewer.scene.primitives.remove(riverInstance);
riverInstance = null;
} else {
console.warn("没有水面实例需要移除.");
}
}
这样写完以后发现效果还是差不多,内存消耗没有什么太大的变化。
问题发现
深入探究后,通过性能分析工具(如Chrome DevTools)查看了调用栈,发现频繁出现与数据劫持相关的方法调用,如 defineReactive、getter 和 setter。这说明Vue2的响应式系统在处理大量数据更新时引入了额外的性能开销。
性能分析结果
通过性能分析工具可以看到以下几点:
-
高CPU使用率:在性能面板中,JavaScript线程使用率很高,说明大量的getter和setter调用在消耗资源。
-
调用栈中频繁出现getter和setter:在调用栈中,可以看到 defineReactive、getter 和 setter 频繁出现,表明Vue的响应式系统在频繁工作。
-
垃圾回收频率高:高频率的数据更新和深度监听会导致内存占用增加,触发更频繁的垃圾回收操作。
这些迹象表明,Vue的 Object.defineProperty 机制在处理复杂数据结构和频繁更新时,确实带来了性能瓶颈。
解决思路
- 使用 Object.freeze
通过 Object.freeze 冻结对象,阻止Vue对其进行响应式处理,但是你也无法对对象进行修改了,所以这里有点不太适用
data() {
return {
frozenObject: Object.freeze({
property1: 'value1',
property2: 'value2'
})
};
}
2.将非响应式数据存储在 this.$data 之外
在Vue实例的 data 选项之外存储数据,Vue不会对其进行响应式处理。
this.nonReactiveObject = null
export default {
data() {
return {
reactiveProperty: 'value'
};
},
created() {
}
};
3.直接在组件实例上添加非响应式属性
在Vue实例的 data 选项之外存储数据,Vue不会对其进行响应式处理。
export default {
data() {
return {
reactiveProperty: 'value'
};
},
created() {
this.nonReactiveProperty = 'value';
}
};
总结
通过以上优化措施,可以显著降低Vue2响应式系统的性能开销,改善Cesium应用中的性能问题。虽然最终效果可能还需要根据具体应用场景进行调整和测试,但这些方法提供了明确的优化方向。
注意:在性能优化过程中,应结合实际应用场景和需求,逐步进行测试和调整,以确保最终的解决方案既满足功能需求,又能够显著提升性能