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

在 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。

相关推荐
三*一2 小时前
Mapbox‑GL JS 多边形折点编辑工具:实现原理、功能拆解与踩坑总结
开发语言·javascript·ecmascript
IT_陈寒3 小时前
Java开发实战:从入门到精通
前端·人工智能·后端
FungLeo3 小时前
成为全栈·基础补充·Node.js 运行时:事件循环与异步
javascript·node.js·事件循环·后端开发·异步编程·成为全栈
风骏时光牛马4 小时前
生成一个AI转岗经历标题,只需要一个标题,不需要其他的任何文本。
前端
Penge6664 小时前
《Go 后端开发者学 TypeScript:类型别名》
javascript
Penge66611 小时前
《Go 后端开发者学 TypeScript:数组和元组》
javascript
Penge66612 小时前
《Go 后端开发者学 TypeScript:export关键字大全》
javascript
Penge66612 小时前
《Go 后端开发者学 TypeScript:从零理解 async、await 与 Promise》
javascript
Penge66612 小时前
typescript 中const用法
javascript
Csvn13 小时前
Monorepo
前端