javascript中无法通过原型链直接实现真正只读属性,但可通过object.defineproperty在原型上定义无setter的getter或writable:false的数据属性,配合object.freeze(this)冻结实例或es2022私有字段+#name+公共getter实现可靠只读。

JavaScript 中无法通过原型链直接实现真正意义上的“只读属性保护”,但可以结合 Object.defineProperty 与原型链的属性查找机制,让实例对象**看似不可写**——即对属性赋值时静默失败(非严格模式)或报错(严格模式),同时确保该行为在继承链中一致生效。
利用原型上定义的 getter + 无 setter 实现“只读”语义
这是最常用且符合原型链特性的做法:在原型对象上为属性定义只有 get、没有 set 的访问器属性。当实例尝试赋值时,因原型上无 setter,JS 引擎会直接在实例自身创建一个同名数据属性(覆盖原型访问器),从而破坏“只读”效果。因此必须配合 writable: false 或防止自有属性被添加。
- 在构造函数原型上使用
Object.defineProperty定义只读访问器:
function Person(name) {
this._name = name;
}
Object.defineProperty(Person.prototype, 'name', {
get() {
return this._name;
},
// 不提供 set,且 configurable: false 防止被删除重定义
configurable: false
});
此时 new Person('Alice').name = 'Bob' 在非严格模式下不报错但无效(仍读出 'Alice'),严格模式下会抛 TypeError。但注意:若后续执行 obj.name = 'xxx',会在 obj 自身添加一个可写的 name 数据属性,之后读写都走实例自身,绕过原型 getter —— 这不是理想结果。
用 Object.defineProperty 冻结原型上的数据属性
如果属性值是基础类型或无需动态计算,更稳妥的方式是在原型上定义**不可写、不可配置的数据属性**:
- 直接在原型上定义
value并设writable: false, configurable: false:
function Animal(type) {
this._type = type;
}
Object.defineProperty(Animal.prototype, 'type', {
value: 'unknown', // 初始值(也可从实例推导,但需额外逻辑)
writable: false,
enumerable: true,
configurable: false
});
// 子类继承后,所有实例共享该只读 type 属性
这样任何对 instance.type = ... 的赋值都会失败(严格模式报错,非严格模式静默忽略),且无法通过 delete instance.type 删除,因为原型属性不可配置。
配合 Object.preventExtensions / Object.seal / Object.freeze 控制实例自身
仅靠原型无法阻止用户在实例上添加同名自有属性来“覆盖”只读行为。要强化保护,可在创建实例后冻结其自身:
- 在构造函数末尾调用
Object.freeze(this),禁止添加/删除/修改任何自有属性: - 或使用
Object.seal(this)(禁止增删,允许改已有属性值 —— 但配合原型只读定义,实际也限制了修改):
function ReadOnlyPoint(x, y) {
this._x = x;
this._y = y;
Object.freeze(this); // 实例一创建就不可变
}
Object.defineProperty(ReadOnlyPoint.prototype, 'x', {
get() { return this._x; }
});
Object.defineProperty(ReadOnlyPoint.prototype, 'y', {
get() { return this._y; }
});
此时即使有人试图 point.x = 99,也会因实例已被冻结而报错(严格模式)或无效(非严格模式),且无法在实例上新增 x 属性覆盖原型 getter。
现代替代方案:使用 # 私有字段 + 公共 getter(ES2022+)
若目标环境支持,私有字段是更干净的只读封装方式,天然避免原型污染和自有属性覆盖问题:
- 私有字段只能在类内部访问,外部无法读写;配合公共 getter 暴露只读接口:
class User {
#name;
constructor(name) {
this.#name = name;
}
get name() {
return this.#name;
}
// 不提供 set name → 外部无法修改
}
const u = new User('Tom');
u.name = 'Jerry'; // 静默失败(非严格)或 TypeError(严格)
console.log(u.name); // 'Tom'
私有字段不参与原型链查找,完全隔离于原型机制,但语义上更接近“只读属性”的设计意图,且无覆盖风险。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











