Appearance
2692. 使对象不可变
ts
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)
))
}
return new Proxy(obj, proxyHandler) as T
}Object.freeze 为什么不够
Object.freeze 是浅层不可变。它阻止顶层属性的修改和新增,但嵌套对象仍然可变:
js
const obj = Object.freeze({ a: { b: 1 } });
obj.a.b = 2; // ✅ 不会报错,嵌套对象没有被 freeze要实现深度不可变,需要递归冻结所有嵌套对象。这道题用 Proxy 而非 Object.freeze 来实现——区别在于 Proxy 可以惰性地拦截访问,而不需要初始化时遍历整个对象树。
两层代理
代码中有两个 ProxyHandler:
proxyHandler 拦截对象的 get 和 set:
get获取属性值,如果值是对象就递归 proxify——惰性深度代理set禁止任何修改,根据 target 是数组还是对象抛出不同的错误
methodHandler 拦截数组变异方法的调用,通过包装方法为 Proxy 实现。当调用 arr.push(1) 时,实际上是先 get 到 push 方法(返回一个 Proxy),再 apply 调用它(被 methodHandler 拦截抛错)。
get 中的无声递归
注意 proxify 对非对象的子属性不创建 Proxy,对对象子属性则创建新 Proxy。这意味着每次访问嵌套对象都会产生一个新的 Proxy 实例——这种惰性策略的代价是:对同一嵌套对象的多次访问会产生不同的 Proxy 包装器,严格相等比较 === 会失败。
