Setter 防护核心在于内部校验与隔离:所有写入必须经 Setter,禁止暴露原始属性;立即校验类型/范围/结构;对象数组需深拷贝并冻结;Getter 返回不可变快照。

Setter 本身不是拦截器,也不能直接防范宏任务期间的数据篡改。真正起作用的是:在 Setter 中嵌入校验逻辑 + 阻断非法赋值 + 隔离数据引用。宏任务(如 setTimeout、setInterval)只是执行环境,篡改是否发生,取决于你是否放行了不安全的写入。
核心原则:不信任任何外部输入,只信任经过校验和封装的写入路径
宏任务中调用的代码,和同步代码一样,只要能访问到你的 setter 属性,就可能触发赋值。关键不是“宏任务特殊”,而是“谁在赋值、值是否合规”。所以防护重心必须落在 setter 内部逻辑上:
- 所有写入必须经过 setter,禁止暴露原始属性(如
this._data)供外部直接修改 - setter 中立即做类型、范围、结构合法性检查,不合规值直接抛错或静默拒绝
- 对对象/数组类值,必须深拷贝后存储,避免外部通过引用反向污染
- 不返回可变副本(如
return this._data),get 方法只提供冻结后的快照或浅拷贝
防止宏任务中“悄悄改值”的典型场景与应对
常见风险不是宏任务本身危险,而是它常被用于异步回调、定时更新、事件响应等场景,容易绕过表单校验或状态管理流程。例如:
- 一个
setTimeout(() => obj.value = 'hacked', 100)直接调用 setter —— 若 setter 没校验,就成功篡改 - 第三方库在
requestAnimationFrame回调里批量更新数据 —— 若你的 setter 允许任意对象传入且未冻结,内部结构可能被改 - WebSocket 收到消息后,在
onmessage(本质是宏任务)里调用setState(data)—— data 若含原型污染字段,可能破坏实例行为
应对方式不是禁用宏任务,而是在 setter 中堵住这些入口:
- 对字符串值,检查是否含控制字符或脚本片段(如
<script></script>) - 对数字,限定有效区间(如
value >= 0 && value ) - 对对象,用
Object.freeze(Object.assign({}, input))存储,或用structuredClone隔离 - 对数组,校验长度、元素类型,并拒绝
__proto__、constructor等敏感键
配合状态标记,识别非预期调用上下文(可选增强)
如果业务确实需要区分“合法更新”和“后台宏任务更新”,可引入轻量上下文标识,但不依赖调用栈(不可靠):
- 定义一个私有标志位
this._isTrustedUpdate = false - 在受信流程中(如用户点击保存、API 响应解析完成),手动设为
true,执行完立刻重置 - setter 中若检测到
!this._isTrustedUpdate,可加日志、降级处理或拒绝更新 - 注意:该机制仅作辅助审计,不能替代基础校验,因为标志位本身也可能被篡改
不推荐的做法
以下思路看似“严格”,实则无效或有害:
- 试图在 setter 中检测是否处于宏任务(无法可靠实现,
setTimeout和Promise.then的执行环境无本质区别) - 给每个属性加
Object.defineProperty并设writable: false—— 这会让属性彻底不可写,失去 setter 意义 - 在 setter 里同步执行耗时校验(如查数据库、发网络请求)—— 会阻塞主线程,违背响应式设计初衷
- 依赖
console.trace()或堆栈分析判断调用来源 —— 易被绕过,且影响性能










