JavaScript中如何递归冻结对象?是否有语言特性或通用实现方式?
好问题!Object.freeze()本身是浅冻结,只能锁定对象自身的直接属性,不会递归处理嵌套的引用类型(比如你例子里的engine对象),所以才会出现修改嵌套属性依然有效的情况。下面给你讲清楚递归冻结对象的正确实现方式和相关细节:
1. 基础递归冻结实现
我们可以写一个自定义函数,遍历对象的所有可枚举属性,对每个引用类型的属性递归调用冻结,最后再冻结当前对象。
function deepFreeze(obj) { // 先处理非对象的情况:如果传入的不是对象或者是null,直接返回 if (typeof obj !== 'object' || obj === null) { return obj; } // 遍历对象的所有可枚举属性 Object.keys(obj).forEach(key => { const value = obj[key]; // 递归冻结嵌套的对象/数组 deepFreeze(value); }); // 冻结当前对象 return Object.freeze(obj); }
用你的例子测试:
const engine = {"power": 200}; const car = {"brand": "Fiat", "motor": engine}; // 递归冻结整个car对象及其嵌套对象 deepFreeze(car); // 现在尝试修改嵌套属性:严格模式下会抛出TypeError,非严格模式下静默失败 car.motor.power = 400; console.log(car.motor.power); // 输出200,修改无效
2. 更健壮的完善版本
上面的基础版本已经能处理大部分场景,但如果要覆盖更多边界情况(比如不可枚举属性、数组、Symbol属性),可以优化函数:
function deepFreeze(obj) { if (typeof obj !== 'object' || obj === null) { return obj; } // 先处理数组的情况(数组也是对象,需要冻结其元素) if (Array.isArray(obj)) { obj.forEach(item => deepFreeze(item)); } else { // 获取所有属性,包括不可枚举和Symbol类型的属性 Reflect.ownKeys(obj).forEach(key => { const value = obj[key]; // 只对对象类型(排除函数,因为函数一般不需要冻结)递归处理 if (typeof value === 'object' && value !== null && typeof value !== 'function') { deepFreeze(value); } }); } return Object.freeze(obj); }
这个版本做了这些优化:
- 处理数组类型,递归冻结数组的每一个元素
- 使用
Reflect.ownKeys()获取所有属性(包括不可枚举属性和Symbol作为键的属性) - 排除了函数类型的属性(如果你的场景需要冻结函数,可以去掉这个判断)
3. 原生语言特性说明
目前JavaScript没有原生的深冻结API,Object.freeze()、Object.seal()、Object.preventExtensions()都是浅操作。所以递归冻结只能通过自定义函数实现,或者使用成熟的工具库(比如Lodash的_.deepFreeze就是深冻结实现,但如果不想引入库,自定义函数是最通用的方式)。
另外要注意:
- 冻结是不可逆的,一旦对象被冻结,就无法再解冻
- 在严格模式下,尝试修改冻结对象的属性会抛出
TypeError,非严格模式下会静默失败 - 冻结对象的原型链不会被影响,如果需要冻结原型,需要单独处理
内容的提问来源于stack exchange,提问作者M. Twarog
相关产品推荐
相关产品推荐

