JS怎么让JSON的值不能改变?深度解析对象冻结与不可变性实现
在JavaScript开发中,JSON(JavaScript Object Notation)作为一种轻量级的数据交换格式,被广泛应用于数据存储和传输,JSON对象的默认可变性(即其属性值可以被修改、添加或删除)有时会带来问题——比如在状态管理、数据安全或需要确保数据不被意外篡改的场景下,我们需要让JSON的值保持“不可改变”,JS中究竟有哪些方法可以实现这一点呢?本文将解析几种主流的实现方式及其原理。
理解JSON与JS对象的本质差异
首先需要明确:JSON和JavaScript对象(Object)并非同一概念,JSON是一种数据格式,本质上是字符串,其语法规则是“键名必须用双引号包裹,值只能是字符串、数字、布尔值、null、数组或JSON对象”;而JS对象是JS语言的一种数据结构,键名可以用单引号或双引号(甚至不用引号,但需符合标识符命名规则),值可以是任意JS数据类型(包括函数、Symbol、undefined等)。
当我们说“让JSON的值不能改变”时,实际操作的对象通常是JS对象(因为JSON作为字符串本身是不可变的,但解析后的JS对象是可变的),本文的讨论将围绕“如何让JS对象(模拟JSON数据)的属性值不可改变”展开。
实现不可变性的核心方法:对象冻结与密封
JS提供了多种操作对象“可变性”的方法,核心是通过Object内置对象的方法控制对象属性的修改、删除或新增,最常用的是Object.freeze()、Object.seal()和Object.preventExtensions(),它们对对象可变性的控制程度不同。
Object.freeze():完全冻结对象,实现“不可变”
Object.freeze()是JS中最严格的不可变性控制方法,它会“冻结”一个对象,使其不可修改、不可删除、不可扩展,且所有属性的值不可变(包括属性的可枚举性、可配置性等)。
- 禁止修改属性值:无法修改已有属性的值;
- 禁止删除属性:无法使用
delete操作符删除已有属性; - 禁止新增属性:无法为对象添加新属性;
- 禁止修改属性特性:无法通过
Object.defineProperty()修改属性的writable、configurable、enumerable等特性。
示例代码:
const originalObj = {
name: "Alice",
age: 25,
address: {
city: "New York"
}
};
// 冻结对象
const frozenObj = Object.freeze(originalObj);
// 尝试修改属性值
frozenObj.name = "Bob"; // 静默失败(严格模式下会抛出TypeError)
console.log(frozenObj.name); // "Alice"
// 尝试删除属性
delete frozenObj.age; // 静默失败
console.log(frozenObj.age); // 25
// 尝试新增属性
frozenObj.gender = "female"; // 静默失败
console.log(frozenObj.gender); // undefined
// 尝试修改嵌套对象(注意:冻结是“浅层”的)
frozenObj.address.city = "Boston"; // 成功修改!
console.log(frozenObj.address.city); // "Boston"
注意事项:
- 浅层冻结:
Object.freeze()是“浅层冻结”,即仅冻结对象自身的属性,对嵌套对象(如示例中的address)无效,若需实现深层冻结,需递归冻结所有嵌套对象。 - 严格模式:在非严格模式下,修改冻结对象的操作会静默失败;在严格模式下(
"use strict"),会直接抛出TypeError。
Object.seal():密封对象,允许修改但禁止删除/新增
Object.seal()比Object.freeze()稍宽松,它会“密封”一个对象,使其禁止删除属性、禁止新增属性,但允许修改已有属性的值,具体特性:
- 禁止删除属性:无法使用
delete删除已有属性; - 禁止新增属性:无法为对象添加新属性;
- 允许修改属性值:可以修改已有属性的值;
- 属性特性不可变:属性的
configurable会被设为false(即无法通过Object.defineProperty()修改特性,但writable仍可修改)。
示例代码:
const sealedObj = Object.seal(originalObj); // 允许修改属性值 sealedObj.name = "Charlie"; // 成功 console.log(sealedObj.name); // "Charlie" // 禁止删除属性 delete sealedObj.age; // 静默失败 console.log(sealedObj.age); // 25 // 禁止新增属性 sealedObj.gender = "male"; // 静默失败 console.log(sealedObj.gender); // undefined
适用场景:
当需要允许修改对象的某些属性值,但防止属性被意外删除或新增时,可以使用Object.seal(),配置对象允许修改参数值,但禁止增删配置项。
Object.preventExtensions():防止扩展,禁止新增属性
Object.preventExtensions()是最宽松的控制方法,它仅阻止对象“扩展”,即禁止新增属性,但允许修改已有属性的值、删除已有属性,具体特性:
- 禁止新增属性:无法为对象添加新属性;
- 允许修改和删除属性:可以修改已有属性的值,也可以使用
delete删除属性。
示例代码:
const nonExtensibleObj = Object.preventExtensions(originalObj); // 允许修改属性值 nonExtensibleObj.name = "David"; // 成功 console.log(nonExtensibleObj.name); // "David" // 允许删除属性 delete nonExtensibleObj.age; // 成功 console.log(nonExtensibleObj.age); // undefined // 禁止新增属性 nonExtensibleObj.gender = "female"; // 静默失败 console.log(nonExtensibleObj.gender); // undefined
适用场景:
当需要确保对象不会新增属性,但允许对现有属性进行修改或删除时,可以使用此方法,动态处理数据时,防止代码意外添加无关属性。
实现深层不可变性:递归冻结嵌套对象
如前所述,Object.freeze()、Object.seal()、Object.preventExtensions()都是“浅层操作”,对嵌套对象无效,若需实现对象的“完全不可变性”(包括所有嵌套属性),需要递归冻结所有嵌套对象。
深层冻结实现代码:
function deepFreeze(obj) {
// 1. 获取对象自身的所有属性(包括Symbol属性)
const propNames = Reflect.ownKeys(obj);
// 2. 递归冻结每个属性值(如果是对象)
for (const name of propNames) {
const value = obj[name];
if (value && typeof value === "object" && !Object.isFrozen(value)) {
deepFreeze(value); // 递归冻结嵌套对象
}
}
// 3. 冻结当前对象
return Object.freeze(obj);
}
// 测试深层冻结
const deepFrozenObj = deepFreeze({
name: "Eve",
details: {
age: 30,
hobbies: ["reading", "coding"]
}
});
// 尝试修改嵌套对象
deepFrozenObj.details.age = 31; // 静默失败(严格模式下报错)
deepFrozenObj.details.hobbies.push("gaming"); // 静默失败(严格模式下报错)
console.log(deepFrozenObj.details); // { age: 30, hobbies: ["reading", "coding"] }
注意事项:
-
递归终止条件:需判断属性值是否为对象且未被冻结,避免无限递归(如循环引用的对象)。
-
循环引用处理:若对象存在循环引用(如
obj.self = obj),直接递归会导致栈溢出,此时需使用WeakMap记录已处理的对象,避免重复冻结:function deepFreezeWithCycle(obj, weakMap = new WeakMap()) { if (weakMap.has(obj)) return obj; // 已处理过,直接返回 weakMap.set(obj, true); // 标记为已处理 const propNames = Reflect.ownKeys(obj); for (const name of propNames) { const value = obj[name]; if (value && typeof value === "object") { deepFreezeWithCycle(value, weakMap); // 传入weakMap } } return Object.freeze(obj); }
其他不可变性实现方案
除了使用Object内置方法,还可以通过以下方式实现不可变性:
使用不可变数据结构库(如Immutable.js)
对于复杂的数据操作(如频繁的增删改查),手动实现深层冻结可能效率较低,此时可以使用专门的不可变数据结构库,如Facebook的`Immutable



还没有评论,来说两句吧...