JavaScript Proxy.revocable:外层访问撤销了,先前取出的对象为什么还能修改

前天 3阅读

给临时编辑会话提供一个可撤销代理,结束时调用 revoke,后续通过代理读写会报错。可是如果调用方早已取出 proxy.nested,保存了里面的普通对象,撤销外层代理并不会让那份引用一起失效。

Proxy.revocable 返回proxy和revoke两个成员。撤销操作切断的是这个代理内部通往目标与处理器的连接,没有遍历目标图,也没有把所有曾返回的对象变成可撤销对象。先认清这一层边界,才能决定它是否适合某个临时访问接口。

下例在 Node.js v24.19.0 实跑,保存为 demo.mjs并执行node demo.mjs。只创建普通内存对象与一个空处理器代理,没有网络、文件或权限系统。

JavaScript Proxy.revocable:外层访问撤销了,先前取出的对象为什么还能修改

AI模型生成概念示意:外层查看通道被切断,先前取出的独立物件仍留在桌上,表示已经保留的引用;不是权限管理界面。

完整实验程序

import assert from 'node:assert/strict';

const target = { nested: { count: 1 }, label: 'draft' };
const { proxy, revoke } = Proxy.revocable(target, {});
const retained = proxy.nested;
const alias = proxy;
assert.equal(retained, target.nested);
console.log('before', proxy.label, retained.count);

revoke();
revoke();
console.log('after typeof', typeof proxy);
console.log('same identity', proxy === alias);

const operations = {
  read: () => proxy.label,
  keys: () => Object.keys(proxy),
  write: () => { proxy.label = 'published'; }
};
for (const [name, operation] of Object.entries(operations)) {
  assert.throws(operation, TypeError);
  console.log(name, 'TypeError');
}

retained.count = 2;
assert.equal(target.nested.count, 2);
assert.equal(target.label, 'draft');
console.log('retained count', retained.count);
console.log('target count', target.nested.count);
console.log('target label', target.label);

本地实际输出

before draft 1
after typeof object
same identity true
read TypeError
keys TypeError
write TypeError
retained count 2
target count 2
target label draft

撤销改变后续操作,没有抹去对象身份

before 输出draft和一,表示撤销前通过代理读取成功。连续调用revoke两次没有报错;第二次撤销不会重新连接目标,也不需要再执行一遍释放过程。

撤销后typeof仍为object,proxy与alias仍严格相等。对象引用没有变成undefined,也没有凭空换成一个新值。这类不需要正常访问目标属性的操作,与真正的读取、枚举和写入必须分开看。

接下来的read、keys、write都由断言确认抛出TypeError。程序不依赖具体错误消息,因为不同运行时的措辞可以变化。真实调用端应把会话结束作为明确状态处理,而不是用typeof等宽泛检查来判断代理是否仍可使用。

已交出去的内部对象还在原来的引用关系里

retained是在撤销前读到的target.nested本体,代码明确断言二者为同一对象。撤销之后给retained.count赋值二,target.nested.count也变成二;目标标签却仍是draft,因为经由代理写published的操作已经失败。

如果接口只想交付一个稳定的数据结果,可以先决定要返回哪些值并创建独立表示;如果需要对多层访问都维持可撤销语义,则必须设计覆盖相应对象关系的包装策略。仅把根对象套上一层Proxy,并不会自动实现深层控制。

复制和撤销也各有用途:已经复制出去的内容不会被撤销追回,仍然直接持有target的人也能继续访问target。不要把这个语言机制当成对任意调用方的完整安全权限边界。它更适合有清楚生命周期、能约束暴露接口的程序内部协作。

测试临时视图时,除了正常结束后的根属性访问,还应保留一份预先取出的嵌套引用再试一次。本例用几条断言就能显示根代理与内部对象的不同去向,比只验证“revoke执行过”更能说明接口实际保证了什么。

参考资料

官方资料核验日期:2026-10-02。以上输出来自文中完整程序,断言通过,退出状态为零。

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。