文章目录

JavaScript 对象属性描述符与不可扩展对象

发布于 2026-04-08 12:24:32 · 浏览 6 次 · 评论 0 条

JavaScript 对象属性描述符与不可扩展对象

在 JavaScript 中,对象不仅仅是一个键值对的集合。每一个属性背后都隐藏着一套控制机制,决定了这个属性是否可以被修改、被遍历或被删除。掌握这些机制,能让你精确控制对象的行为,防止代码被意外篡改。


1. 查看属性的“身份证”

每个属性都有一个“描述符”对象,它记录了该属性的元数据。默认情况下,直接通过字面量创建的属性,其描述符通常是特定的默认值。

打开浏览器的开发者工具控制台(Console),输入以下代码并按下回车:

const person = {
    name: "Alice"
};

// 获取 name 属性的描述符
const descriptor = Object.getOwnPropertyDescriptor(person, 'name');

console.log(descriptor);

此时控制台会输出一个对象,包含 value(值)、writable(是否可写)、enumerable(是否可枚举)、configurable(是否可配置)这四个核心属性。


2. 精细控制属性特征

通过 Object.defineProperty 方法,你可以修改或定义属性的这些特征。这是修改属性“底层设置”的最底层 API。

复制并在控制台运行以下代码:

const product = {};

// 定义一个 price 属性
Object.defineProperty(product, 'price', {
    value: 99.9,
    writable: false,  // 设置为不可写
    enumerable: true, // 设置为可枚举
    configurable: false // 设置为不可配置
});

console.log(product.price); // 输出 99.9

product.price = 199.9;     // 尝试修改
console.log(product.price); // 依然是 99.9,因为 writable 为 false

在上面的代码中,即使尝试对 price 赋值新数值,由于我们将 writable 设置false,修改操作会静默失败(在非严格模式下)。如果是在严格模式下(use strict),这行代码会抛出错误。

理解以下四个关键字至关重要:

属性名 作用说明
value 属性的具体数值。
writable false 时,属性值无法被修改(只读)。
enumerable false 时,属性不会出现在 for...in 循环或 Object.keys() 中。
configurable false 时,属性无法被删除,且无法修改其描述符(除了将 writabletrue 改为 false)。

3. 使用存取器保护数据

除了直接存储值,属性还可以通过 getter 和 getter 函数来动态计算值或拦截赋值操作。这类似于其他语言中的“私有变量”保护模式。

输入以下代码体验存取器:

const account = {
    _balance: 1000 // 约定以下划线开头表示“内部”属性
};

Object.defineProperty(account, 'balance', {
    get: function() {
        return this._balance;
    },
    set: function(value) {
        if (value < 0) {
            console.error("余额不能为负数");
            return;
        }
        this._balance = value;
    },
    enumerable: true
});

account.balance = 500;    // 正常修改
console.log(account.balance); // 输出 500

account.balance = -100;   // 尝试设为负数
console.log(account.balance); // 依然输出 500,并被拦截

注意,存取器属性(getter/setter)不包含 valuewritable 属性。


4. 锁定对象的三个层级

JavaScript 提供了三个逐步增强的方法,用来限制对对象结构的修改,从“禁止新增”到“完全冻结”。

4.1 防止扩展:Object.preventExtensions

这是最轻量级的锁定。它阻止向对象添加新属性,但现有属性依然可以修改和删除。

运行以下代码测试:

const obj1 = { a: 1 };
Object.preventExtensions(obj1);

obj1.b = 2;       // 尝试添加新属性(静默失败)
console.log(obj1.b); // 输出 undefined
obj1.a = 2;       // 修改现有属性(成功)
console.log(obj1.a); // 输出 2

4.2 密封对象:Object.seal

密封相当于在“防止扩展”的基础上,将所有现有属性的 configurable 标记false。这意味着你不能添加新属性,也不能删除现有属性。

运行以下代码测试:

const obj2 = { a: 1 };
Object.seal(obj2);

delete obj2.a;   // 尝试删除属性(静默失败)
console.log(obj2.a); // 输出 1
obj2.a = 2;       // 修改属性值(成功,只要 writable 为 true)
console.log(obj2.a); // 输出 2

4.3 冻结对象:Object.freeze

这是最高级别的锁定。它调用 Object.seal,同时将所有现有属性的 writable 设置false。对象变为完全只读。

运行以下代码测试:

const obj3 = { a: 1 };
Object.freeze(obj3);

obj3.a = 2;       // 尝试修改(失败)
delete obj3.a;    // 尝试删除(失败)
obj3.b = 3;       // 尝试添加(失败)
console.log(obj3); // 输出 { a: 1 }

5. 对象锁定层级可视化

为了直观理解这三者的区别与包含关系,请参考下方的层级图。随着层级的加深,对象的权限越来越小,安全性越来越高。

graph TD A["普通对象\n可增删改"] -->|防止扩展| B["不可扩展对象\n不可新增"] B -->|密封| C["密封对象\n不可新增\n不可删除\n不可配置"] C -->|冻结| D["冻结对象\n不可新增\n不可删除\n不可配置\n不可修改值"]

6. 快速对比表

下表总结了这三种锁定方式对属性操作的具体影响:

操作类型 普通对象 preventExtensions seal (密封) freeze (冻结)
读取
修改值 ✅*
新增属性
删除属性
修改描述符

*注:如果属性的 writable 被手动设为 false,则密封对象也不能修改值。

使用 Object.isExtensible()Object.isSealed()Object.isFrozen() 可以分别检查对象当前处于哪种状态。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文