javascript 中可通过 object.defineproperty 定义 get/set 访问器属性实现属性读写拦截,但仅限已声明属性,无法拦截新增属性、枚举或删除操作;全量动态拦截应使用 proxy。

JavaScript 中没有直接的“属性访问拦截器”,但可以通过 属性描述符(Property Descriptor)配合 Object.defineProperty 或 Object.defineProperties,在定义属性时注入自定义逻辑,实现对读取(get)和写入(set)的拦截。这本质上是利用访问器属性(accessor property)机制,而非数据属性(data property)。
使用 get/set 描述符定义拦截式属性
访问器属性不存储值,而是通过 get 和 set 函数控制访问行为。每次读取或赋值该属性时,都会触发对应函数:
-
get函数在属性被读取时执行,返回值即为属性值 -
set函数在属性被赋值时执行,参数为新值,可用于验证、转换或副作用 - 必须同时指定
configurable(是否可删除/重定义)和enumerable(是否可枚举),否则默认为false
示例:记录访问日志并过滤非法值
const obj = {};Object.defineProperty(obj, 'age', {
get() { console.log('age 被读取'); return this._age; },
set(value) {
if (typeof value !== 'number' || value 150) {
throw new Error('age 必须是 0–150 的数字');
}
console.log(`age 设置为 ${value}`);
this._age = value;
},
configurable: true,
enumerable: true
});
批量定义多个拦截属性
用 Object.defineProperties 可一次性为对象设置多个带拦截逻辑的属性,避免重复调用 defineProperty:
- 传入目标对象和一个属性描述符对象(键为属性名,值为描述符)
- 每个属性可独立配置自己的
get/set行为 - 适合封装一组关联字段(如表单模型)
示例:用户信息对象中拦截 name 和 email
const user = { _name: '', _email: '' };Object.defineProperties(user, {
name: {
get() { return this._name.trim(); },
set(val) { this._name = String(val).trim(); },
configurable: true,
enumerable: true
},
email: {
get() { return this._email.toLowerCase(); },
set(val) {
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val)) {
throw new Error('邮箱格式不合法');
}
this._email = val;
},
configurable: true,
enumerable: true
}
});
与 Proxy 的关键区别
描述符拦截只作用于**已明确声明的属性**,无法捕获未定义属性的访问或遍历操作:
- 对不存在的属性(如
obj.xxx)读取,不会触发任何get;只会返回undefined - 不能拦截
in、for...in、Object.keys()等枚举行为(除非手动设enumerable: true) - 不能拦截删除(
delete)、原型链查找、Object.assign等操作 - 若需全量、动态拦截(包括任意属性名),应使用
Proxy,它才是真正意义上的通用拦截器
注意事项与限制
描述符方式简洁轻量,但有若干约束需留意:
- 只能对**普通对象**或**显式定义的对象属性**生效,不能用于类字段(class fields)语法糖(如
foo = 1),因其本质是数据属性 - 一旦属性被定义为访问器,就不能再用简单赋值(
obj.prop = value)覆盖为数据属性,除非先delete再重定义 -
get/set中若访问自身属性(如this.prop),可能引发无限递归,应操作私有后备字段(如this._prop) - 严格模式下,
set函数若无返回值或返回undefined,赋值表达式结果为undefined;非严格模式下为新值
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











