2692. 使对象不可变
2025年6月25日大约 2 分钟
2692. 使对象不可变
使用 Proxy 和 Reflect 对象进行代理,拦截修改操作
type JSONValue =
| null
| boolean
| number
| string
| JSONValue[]
| { [key: string]: JSONValue }
type Obj = Array<JSONValue> | Record<string, JSONValue>
const METHODS = ['pop', 'push', 'shift', 'unshift', 'splice', 'sort', 'reverse']
const isObject = (obj: any) => typeof obj === 'object' && obj !== null
const proxyHandler: ProxyHandler<any> = {
get: (target, props) => {
// 获取当前访问属性
const val = Reflect.get(target, props)
// 非对象返回值
if (!isObject(val)) return val
// 递归代理
return proxify(val)
},
set: (target, props) => {
//直接修改数组,抛出异常
if (Array.isArray(target)) {
throw `Error Modifying Index: ${String(props)}`
}
// 抛出对象异常修改
throw `Error Modifying: ${String(props)}`
},
}
function makeImmutable(obj: Obj): any {
return proxify(obj)
}
const methodHandler: ProxyHandler<(...args: any[]) => any> = {
apply(target) {
throw `Error Calling Method: ${target.name}`
},
}
function proxify<T extends Obj>(obj: T): T {
if (Array.isArray(obj)) {
// 遍历拦截数组操作方法
METHODS.forEach((method: (typeof METHODS)[number]) => (
(obj as any)[method] = new Proxy((obj as any)[method], methodHandler)
// 直接重写也可以
// (obj as any)[method] = ()=> { throw `Error Calling Method: ${target.name}`}
)
}
return new Proxy(obj, proxyHandler) as T
}
注意点
在数组中的
如果试图调用会改变数组的方法,则会产生以下错误消息: Error Calling Method: ${methodName}
。你可以假设只有以下方法能够改变数组: ['pop', 'push', 'shift', 'unshift', 'splice', 'sort', 'reverse'] 。
调用方法应该在读取方法时拦截(或者直接重写方法)
arr.push(1)
==>
const fn = arr.push;
fn(1)
📊 Proxy 拦截 vs 直接重写 方法对比
对比项 | Proxy 方式(写法 A) | 直接重写(写法 B) |
---|---|---|
性能 | ⚠️ 略慢:多了一层 Proxy 调用 | ✅ 较快:函数直接执行 |
扩展性 | ✅ 高:可记录日志、条件拦截、限制调用等 | ❌ 低:函数写死,只能抛错 |
保留原始函数信息 | ✅ 可访问 target.name 等 | ❌ 不保留原函数引用 |
可维护性 | ✅ 明确使用 Proxy 风格,适合统一代理逻辑 | ✅ 简洁直观,逻辑明确 |
内存使用 | ⚠️ 稍高:每个方法都创建一个新的 Proxy 对象 | ✅ 低:简单闭包函数 |
- 性能优先,逻辑简单时 使用 写法 B
- 需要调试日志或条件拦截时 使用 写法 A