为什么要有访问器? 你不知道的对象属性

在 JavaScript 里,对象的属性其实分为两种截然不同的"物种":数据属性(Data Property) 和 访问器属性(Accessor Property) 。

1.什么是数据属性?(你平时写的那个)

当你写:

javascript 复制代码
class User {
    username: string;
}

或者

javascript 复制代码
const obj = { username: '' };

时,JS 引擎在底层把 username 定义为一个数据属性(Data Property) 。

  • 数据属性里有一个叫 value 的字段,直接存着 ''。
  • 你写 obj.username = 'Alice',引擎是直接去修改那个 value 字段。没有函数参与,没有拦截,简单粗暴,也不需要 get/set。

2. 什么是访问器属性?

访问器属性根本不存值 。它是一个自动售货机 。

你没有储物柜,只有两个按钮:一个叫 get(取货),一个叫 set(存货)。

当你读这个属性时,引擎发现它是个访问器属性,就会去按 get 按钮,执行你写的 getter 函数。

当你写这个属性时,引擎就会去按 set 按钮,执行你写的 setter 函数。

例子(字面量写法):

javascript

javascript 复制代码
const user = {
    _name: 'Alice', // 这是个真正存值的普通属性

    get name() {
        console.log('正在读取 name');
        return this._name;
    },

    set name(newVal) {
        console.log('正在修改 name');
        this._name = newVal;
    }
};

user.name;          // 触发 get,打印:正在读取 name
user.name = 'Bob';  // 触发 set,打印:正在修改 name

这里的 name 就是一个访问器属性,它本身没有值,值存在 _name 里。

3. 核心区别

特征 数据属性 (Data Property) 访问器属性 (Accessor Property)
本质 储物柜 自动售货机
是否有 value 有,直接存值 没有,值由外部或闭包保存
是否有 get/set 没有 有,读写全由函数接管
读写行为 直接读/写内存里的值 触发函数调用,执行代码逻辑
能否自定义行为 不能,赋值就是纯粹赋值 能,可以在读/写时做任何事(校验、日志、计算)

4. 为什么要有访问器属性?它有什么用?

它赋予了开发者 "拦截并控制属性读写" 的超能力。比如:

场景 A:数据校验(防瞎填)

javascript

kotlin 复制代码
const person = {
    _age: 0,
    set age(val) {
        if (val < 0 || val > 150) {
            throw new Error('年龄不合法!');
        }
        this._age = val;
    },
    get age() {
        return this._age;
    }
};
person.age = -5; // 报错:年龄不合法!

场景 B:计算属性(互相依赖)

javascript

arduino 复制代码
const rect = {
    width: 10,
    height: 20,
    get area() {
        return this.width * this.height; // 每次读取,动态计算
    }
};
console.log(rect.area); // 200

area 并不存值,它是通过 width 和 height 实时算出来的。

5. 为什么访问器属性自己没地方存值?

要理解这个,我们要回到 JS 引擎的底层设计。在 JS 中,属性描述符(Property Descriptor)是有固定形状(Shape) 的,它严格分为两套互相排斥的配置:

  • 数据属性 的配置:{ value, writable, enumerable, configurable }
  • 访问器属性 的配置:{ get, set, enumerable, configurable }

JS 引擎有一条铁律:绝对不允许同时出现 value 和 get/set。

如果你强行在 Object.defineProperty 里同时写:

javascript

javascript 复制代码
Object.defineProperty(target, 'username', {
    value: '游客', // 试图存值
    get() { return 'Alice'; } // 试图拦截
});
// 引擎会直接抛出 TypeError,报错说:Invalid property descriptor. Cannot both specify accessors and a value or writable attribute.

为什么引擎要这么设计?(结合比喻)

  • 数据属性 就像一个储物柜 。引擎分配了一块内存存 value。
  • 访问器属性 就像一个自动售货机 。它只提供了 get(出货按钮)和 set(进货按钮)。

如果你把属性定义成了访问器属性(安装上了按钮),引擎就会在底层把这个属性标记为"访问器"。

此时,它不再分配用来存 value 的内存槽位 。

如果强行赋值 u2.username = 'Alice',引擎只会去按 set 按钮。如果没写 set,引擎既没按钮可按,又没 value 可改,只能报错。

所以,不是它"没地方存",而是 JS 引擎在底层强行把它的"存储槽位"砍掉了,逼迫你必须自己找地方存。 最常见的地方就是闭包变量 或者WeakMap。

相关推荐
OpenTiny社区1 小时前
HC 2026 回顾|OpenTiny NEXT 解锁 Web 应用智能化新范式
前端·开源·github
时光少年1 小时前
Android HWC退化与防治方法
前端
涛涛ing1 小时前
Remix 3 RC 发布:一个不再依赖 React 的全栈框架,正在重新定义“元框架”的边界
前端
闪耀之光M781 小时前
Vite配置文件解析
前端
PC2005_cloud1 小时前
RabbitMQ 在 Spring Boot 中的完整使用
前端·后端
掘金挖土1 小时前
前端手摸手跑路之 AI 应用开发(八)
前端·后端
风骏时光牛马1 小时前
产品功能可用性测试报告
前端
骑着蜗牛撵大象3271 小时前
Jev 使用完整指南:从申请 API Key 到置信度路由,把 TypeSafe 决策模型接进自己的代码
前端
CopyCode1 小时前
我把 Cursor 接到了蓝湖上,设计师再也不用追着我问"还原了吗"
前端