如何在 JavaScript 中实现一个只读属性
方法一:Object.defineProperty()
使用属性描述符 (Property Descriptors) 来精确控制属性的行为。这是最根本的实现方式。
方式 1: 使用数据描述符 writable: false
通过将属性的 writable 特性设置为 false,来禁止对属性值的修改。
核心代码:
javascriptconst obj = {}; Object.defineProperty(obj, 'name', { value: 'Kevin', writable: false, // configurable 默认为 false // enumerable 默认为 false }); console.log(obj.name); // 输出: "Kevin" // 尝试修改属性值 obj.name = 'Eric'; // 在非严格模式下静默失败,严格模式下会抛出 TypeError console.log(obj.name); // 依然输出: "Kevin"重点:
- 属性描述符中的
configurable默认值为false。 - 当
configurable: false时,该属性的描述符本身就无法被再次配置(例如,无法将writable从false改回true),属性也无法被删除。这从根本上保证了其只读的稳定性。
- 属性描述符中的
方式 2: 使用存取描述符 (只提供 get 方法)
通过为属性定义一个 getter 但不定义 setter,使得该属性在尝试被写入时,因为没有对应的 setter 操作而写入失败。
核心代码:
javascriptconst obj = {}; let internalValue = 'Kevin'; Object.defineProperty(obj, 'name', { get() { return internalValue; }, // 没有定义 set 方法 // configurable 默认为 false }); console.log(obj.name); // 输出: "Kevin" // 尝试修改属性值 obj.name = 'Eric'; // 静默失败 console.log(obj.name); // 依然输出: "Kevin"重点:
- 同样因为
configurable默认为false,后续无法再通过defineProperty为该属性添加setter或将其改回数据属性。
- 同样因为
关联方式: ES6 Getter 语法糖
在对象字面量中直接使用 get 语法,其底层实现原理与 Object.defineProperty 的存取描述符一致,是一种更简洁的写法。
核心代码:
javascriptconst obj = { get name() { return 'Kevin'; } }; console.log(obj.name); // 输出: "Kevin" obj.name = 'Eric'; // 尝试修改,静默失败 console.log(obj.name); // 依然输出: "Kevin"
方法二:Proxy
使用 Proxy 创建一个对象的代理。在代理层面拦截对属性的 set 操作,从而阻止修改原始对象。
核心代码:
javascriptconst target = { name: 'Kevin' }; const proxy = new Proxy(target, { get(target, prop, receiver) { return Reflect.get(target, prop, receiver); }, set(target, prop, value, receiver) { // 拦截 set 操作,不做任何事或抛出错误 console.warn(`Warning: Property '${prop}' is read-only.`); return true; // 返回 true 表示操作“成功”,但我们并未执行赋值 } }); console.log(proxy.name); // 输出: "Kevin" proxy.name = 'Eric'; // 输出警告: Warning: Property 'name' is read-only. console.log(proxy.name); // 依然输出: "Kevin"重点:
- 这种方式的保护作用于代理对象 (
proxy)。必须确保后续所有操作都通过proxy对象进行,如果直接操作原始对象 (target),则无法实现只读。
- 这种方式的保护作用于代理对象 (
方法三:Object.freeze()
冻结整个对象,使其所有自身属性都变得不可修改、不可添加、不可删除。
核心代码:
javascriptconst obj = { name: 'Kevin', age: 30 }; Object.freeze(obj); obj.name = 'Eric'; // 静默失败 obj.age = 31; // 静默失败 obj.location = 'USA'; // 尝试添加新属性,静默失败 console.log(obj.name); // 输出: "Kevin" console.log(obj.age); // 输出: 30 console.log(obj.location); // 输出: undefined重点:
- 影响范围广: 此方法会影响对象上的所有属性,将整个对象变为只读。如果只想保护单个属性,此方法不适用。
- 浅冻结:
Object.freeze()是浅操作。如果对象的属性值是另一个对象,那么这个内层对象本身是不被冻结的,其内部属性可以被修改。