object.freeze仅浅冻结,需deepfreeze递归冻结+切断原型链+早于模块加载执行;否则第三方仍可修改嵌套属性或通过原型污染绕过。

Object.freeze 本身不能彻底防住第三方插件攻击,它只防误改,不防恶意覆盖、原型污染或绕过引用的操作;真正能起作用的是“冻结 + 切断原型 + 早于任何模块加载 + 配合模块封装”的组合动作。
为什么 freeze(config) 后第三方脚本还能改嵌套字段
因为 Object.freeze 是浅冻结:它只锁住 config 自身属性的 writable 和 configurable,但对 config.api、config.features 这类对象值不做处理。第三方脚本只要拿到 config.api 的引用,就能自由修改其内部字段——比如 config.api.timeout = 0 或 config.features.admin = true。
- 常见误判:从 JSON 文件读取后直接
Object.freeze(config),以为万事大吉 - 更危险的是:某些插件会用
Object.setPrototypeOf(config, null)或config.__proto__ = null破坏冻结状态(虽然冻结后setPrototypeOf会失败,但若在 freeze 前已污染原型,就晚了) - 注意:
config被 freeze 后,config.api = {}会失败,但config.api.baseUrl = 'xxx'依然成功
必须用 deepFreeze 递归冻结所有嵌套层级
手动实现一个安全的 deepFreeze,跳过原始值、函数、Date、RegExp 等非配置类对象,并用 WeakMap 防循环引用:
前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。
function deepFreeze(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date || obj instanceof RegExp || typeof obj === 'function') return obj;
const seen = new WeakMap();
function _freeze(o) {
if (o === null || typeof o !== 'object') return o;
if (seen.has(o)) return o;
seen.set(o, true);
Object.getOwnPropertyNames(o).forEach(prop => _freeze(o[prop]));
Object.getOwnPropertySymbols(o).forEach(sym => _freeze(o[sym]));
return Object.freeze(o);
}
return _freeze(obj);
}
- 不要用
JSON.parse(JSON.stringify())做深拷贝再 freeze——会丢掉函数、undefined、Symbol、循环引用,且性能差 - 确保该函数在配置对象完全构造完毕后**立即调用**,不能等
export default之后再 freeze - 如果配置里混入了
Map、Set或Promise,它们不属于 plain object/array,deepFreeze不处理,也不该出现在常量配置中
冻结必须发生在模块加载链最顶端,且切断原型链
Node.js 或现代浏览器 ESM 中,冻结时机错误等于白做。一旦某个模块缓存了未冻结的引用(例如 const cfg = require('./config')),其他模块拿到的就是可变副本。
- 在入口文件(如
index.js或main.ts)最顶部执行:import config from './config.js'; deepFreeze(config); export { config }; - 浏览器中,若用
<script type="module"></script>,需在模块顶层立即执行deepFreeze,不能包裹在函数或事件回调里 - 为防原型污染,在 freeze 前加一句:
Object.setPrototypeOf(config, null),让config脱离Object.prototype,避免被注入toString、valueOf等钩子 - 禁止导出时写成
export default Object.freeze({...})——打包器可能提前优化,导致其他模块拿到未冻结版本
仅靠 freeze 不足以对抗主动攻击,还需运行时防护层
即使完成深冻结和原型切断,恶意脚本仍可能通过以下方式破坏一致性:
- 用
eval或Function构造器动态执行代码,绕过静态冻结 - 替换全局
Object.freeze方法本身(虽然少见,但可行) - 篡改
console、fetch、XMLHttpRequest等 API,伪造响应数据
所以生产环境建议叠加:Object.freeze + Object.preventExtensions(双重保险)+ ESLint 规则 no-param-reassign + no-const-assign + 在关键读取点加 Object.isFrozen(config) 断言(仅开发环境)。真正的防线不在冻结动作本身,而在冻结是否被绕过、是否被提前污染、是否与其他模块共享了同一份未冻结引用——这些细节比写对 deepFreeze 更容易出错。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










