MVVM里JsonOb深度监听的性能优化

1. 防止重复观察同一对象
2.优化数组操作(只监听新增元素)
3.支持冻结数据(不监听静态配置)
4.支持批量更新(减少回调次数)
5.支持自定义深度限制
6.更安全的内存管理
7.API 完全兼容原始版本,可零改动升级
This commit is contained in:
dgflash
2026-03-08 12:09:10 +08:00
parent 2350b06c84
commit 9d65c31016

View File

@@ -2,7 +2,19 @@
* @Author: dgflash
* @Date: 2022-09-01 18:00:28
* @LastEditors: dgflash
* @LastEditTime: 2022-09-06 17:18:05
* @LastEditTime: 2024-03-08 10:00:00
*
* JsonOb 性能优化版本(默认实现)
*
* 优化特性:
* - 防止重复观察同一对象
* - 优化数组操作(只监听新增元素)
* - 支持冻结数据(不监听静态配置)
* - 支持批量更新(减少回调次数)
* - 支持自定义深度限制
* - 更安全的内存管理
*
* API 完全兼容原始版本,可零改动升级
*/
/**
@@ -14,40 +26,93 @@ const types = {
obj: '[object Object]',
array: '[object Array]'
};
const OAM = ['push', 'pop', 'shift', 'unshift', 'sort', 'reverse', 'splice'];
/** 冻结标记 */
const FROZEN_KEY = Symbol('__frozen__');
/** 配置选项 */
export interface JsonObOptions {
/** 最大监听深度,默认 10 */
maxDepth?: number;
/** 冻结的属性列表(不监听这些属性) */
frozenKeys?: string[];
/** 是否启用批量更新,默认 false */
enableBatch?: boolean;
/** 批量更新延迟(毫秒),默认 16ms一帧 */
batchDelay?: number;
}
/**
* 实现属性拦截的类
* 标记对象为冻结状态(不会被监听)
* 适用于静态配置数据,可提升性能
*/
export function freezeData(obj: any): void {
if (typeof obj === 'object' && obj !== null) {
obj[FROZEN_KEY] = true;
}
}
/**
* 检查对象是否被冻结
*/
export function isFrozen(obj: any): boolean {
return obj && obj[FROZEN_KEY] === true;
}
/**
* 实现属性拦截的类(性能优化版)
*/
export class JsonOb<T> {
constructor(obj: T, callback: (newVal: any, oldVal: any, pathArray: string[]) => void) {
constructor(
obj: T,
callback: (newVal: any, oldVal: any, pathArray: string[]) => void,
options?: JsonObOptions
) {
if (OP.toString.call(obj) !== types.obj && OP.toString.call(obj) !== types.array) {
console.error('请传入一个对象或数组');
}
this._callback = callback;
this._root = obj;
this._options = {
maxDepth: 10,
frozenKeys: [],
enableBatch: false,
batchDelay: 16,
...options
};
this._observedObjects = new WeakSet();
this._overriddenArrays = new WeakMap();
this.observe(obj);
}
private _callback;
private _callback: ((newVal: any, oldVal: any, pathArray: string[]) => void) | null;
private _root: T;
private _options: Required<JsonObOptions>;
private _observedObjects: WeakSet<any>;
private _overriddenArrays: WeakMap<any, any>;
private _isDestroyed = false;
// 批量更新相关
private _pendingChanges = new Map<string, { newVal: any, oldVal: any, path: string[] }>();
private _batchTimer: any = null;
/** 对象属性劫持 */
private observe<T>(obj: T, path?: any) {
if (this._isDestroyed) return;
// 检查是否被冻结
if (isFrozen(obj)) return;
// 防止重复观察同一个对象
if (this._observedObjects.has(obj)) return;
this._observedObjects.add(obj);
// 深度限制防止过深递归最大深度10
if (path && path.length > 10) {
console.warn('JsonOb: 对象嵌套深度超过10层停止监听');
// 深度限制
if (path && path.length >= this._options.maxDepth) {
if (path.length === this._options.maxDepth) {
console.warn(`JsonOb: 对象嵌套深度超过${this._options.maxDepth}层,停止监听`);
}
return;
}
@@ -55,110 +120,206 @@ export class JsonOb<T> {
this.overrideArrayProto(obj, path);
}
// @ts-ignore避免API生成工具报错
// @ts-ignore
Object.keys(obj).forEach((key) => {
// 跳过冻结的属性
if (this._options.frozenKeys.includes(key)) {
return;
}
const self = this;
// @ts-ignore
let oldVal = obj[key];
// 创建路径数组的副本,避免引用问题
const pathArray = path ? [...path, key] : [key];
Object.defineProperty(obj, key, {
get: function () {
return oldVal;
},
set: function (newVal) {
if (self._isDestroyed) return;
if (oldVal !== newVal) {
if (OP.toString.call(newVal) === types.obj) {
const ov = oldVal;
oldVal = newVal;
// 如果新值是对象,继续监听
if (OP.toString.call(newVal) === types.obj && !isFrozen(newVal)) {
self.observe(newVal, pathArray);
}
const ov = oldVal;
oldVal = newVal;
// 传递路径数组的副本,防止外部修改
self._callback(newVal, ov, pathArray.slice());
// 触发回调
self.triggerChange(newVal, ov, pathArray);
}
}
},
enumerable: true,
configurable: true
});
// @ts-ignore
const o = obj[key];
if (OP.toString.call(o) === types.obj || OP.toString.call(o) === types.array) {
if ((OP.toString.call(o) === types.obj || OP.toString.call(o) === types.array) && !isFrozen(o)) {
this.observe(o, pathArray);
}
}, this);
}
/**
* 对数组类型进行动态绑定
* @param array
* @param path
* 对数组类型进行动态绑定(优化版)
*/
private overrideArrayProto(array: any, path: any) {
if (this._isDestroyed) return;
// 检查是否已经重写过该数组
if (this._overriddenArrays.has(array)) return;
// 保存原始 Array 原型
const originalProto = Array.prototype;
// 通过 Object.create 方法创建一个对象该对象的原型是Array.prototype
const overrideProto = Object.create(Array.prototype);
const self = this;
// 存储原始原型引用,用于后续恢复
this._overriddenArrays.set(array, originalProto);
// 遍历要重写的数组方法
OAM.forEach((method: any) => {
// 修改型方法
const mutatingMethods = ['push', 'pop', 'shift', 'unshift', 'sort', 'reverse', 'splice'];
mutatingMethods.forEach((method: any) => {
Object.defineProperty(overrideProto, method, {
value: function () {
if (self._isDestroyed) return originalProto[method].apply(this, arguments);
const oldVal = this.slice();
// 调用原始原型上的方法
const result = originalProto[method].apply(this, arguments);
// 继续监听新数组
self.observe(this, path);
// 传递路径数组的副本
self._callback(this, oldVal, path ? path.slice() : path);
// 优化:只监听新增的元素
if (method === 'push' || method === 'unshift') {
const newItems = Array.from(arguments);
const startIndex = method === 'push' ? this.length - newItems.length : 0;
newItems.forEach((item, index) => {
if ((OP.toString.call(item) === types.obj || OP.toString.call(item) === types.array) && !isFrozen(item)) {
const itemPath = path ? [...path, (startIndex + index).toString()] : [(startIndex + index).toString()];
self.observe(item, itemPath);
}
});
} else if (method === 'splice') {
// splice 可能添加新元素
const startIndex = arguments[0];
const newItems = Array.prototype.slice.call(arguments, 2);
newItems.forEach((item, index) => {
if ((OP.toString.call(item) === types.obj || OP.toString.call(item) === types.array) && !isFrozen(item)) {
const itemPath = path ? [...path, (startIndex + index).toString()] : [(startIndex + index).toString()];
self.observe(item, itemPath);
}
});
}
// 触发回调
self.triggerChange(this, oldVal, path ? path.slice() : path);
return result;
},
writable: true,
configurable: true
configurable: true,
enumerable: false
});
});
// 使用 Object.setPrototypeOf 代替直接修改 __proto__(更安全)
// 使用 Object.setPrototypeOf 代替直接修改 __proto__
try {
Object.setPrototypeOf(array, overrideProto);
} catch (e) {
// 降级方案:如果不支持 setPrototypeOf使用 __proto__
// 降级方案
array['__proto__'] = overrideProto;
}
}
/**
* 触发变化回调
*/
private triggerChange(newVal: any, oldVal: any, pathArray: string[]) {
if (this._options.enableBatch) {
this.queueChange(newVal, oldVal, pathArray);
} else {
this._callback?.(newVal, oldVal, pathArray.slice());
}
}
/**
* 批量更新:将变化加入队列
*/
private queueChange(newVal: any, oldVal: any, pathArray: string[]) {
const pathKey = pathArray.join('.');
const existing = this._pendingChanges.get(pathKey);
this._pendingChanges.set(pathKey, {
newVal,
oldVal: existing ? existing.oldVal : oldVal,
path: pathArray
});
this.scheduleBatchUpdate();
}
/**
* 调度批量更新
*/
private scheduleBatchUpdate() {
if (this._batchTimer !== null) return;
this._batchTimer = setTimeout(() => {
this.flushChanges();
}, this._options.batchDelay);
}
/**
* 刷新所有待处理的变化
*/
private flushChanges() {
if (this._isDestroyed || !this._callback) return;
this._pendingChanges.forEach((change) => {
this._callback!(change.newVal, change.oldVal, change.path);
});
this._pendingChanges.clear();
this._batchTimer = null;
}
/**
* 立即刷新所有待处理的变化(仅在启用批量更新时有效)
*/
flush() {
if (this._batchTimer !== null) {
clearTimeout(this._batchTimer);
this._batchTimer = null;
}
this.flushChanges();
}
/**
* 销毁监听,释放内存
* 注意:无法完全恢复属性劫持,但可以停止回调和清理引用
*/
destroy() {
if (this._isDestroyed) return;
this._isDestroyed = true;
// 清空回调引用
// @ts-ignore
this._callback = null;
// 尝试恢复数组原型(只能恢复我们记录的)
// 注意:由于 WeakMap 的特性,这里无法遍历所有数组
// 但当数组被垃圾回收时WeakMap 会自动清理
// 刷新待处理的变化
if (this._options.enableBatch) {
this.flush();
}
// 清理定时器
if (this._batchTimer !== null) {
clearTimeout(this._batchTimer);
this._batchTimer = null;
}
// 清空引用
this._callback = null;
// @ts-ignore
this._root = null;
this._pendingChanges.clear();
}
}