0

0

JS 对象属性描述符 - 配置 writable、enumerable 的特性控制

幻影之瞳

幻影之瞳

发布时间:2025-09-24 23:23:01

|

1103人浏览过

|

来源于php中文网

原创

writable 和 enumerable 是 javascript 属性描述符的核心配置项,分别控制属性值是否可修改及是否可被遍历。通过 object.defineproperty() 可设置 writable: false 防止属性值被更改,enumerable: false 使属性不在 for...in、object.keys() 或 json.stringify() 中出现;直接赋值创建的属性默认两者均为 true,而 defineproperty 创建时未指定则默认为 false,这一差异需特别注意。精准控制这两项可提升代码安全性、封装性和可维护性,常用于定义不可变配置、隐藏内部状态、敏感数据保护及构建清晰的公共 api。

js 对象属性描述符 - 配置 writable、enumerable 的特性控制

JavaScript 对象的属性描述符中的 writableenumerable,是两个核心的配置项,它们决定了属性的修改权限和可枚举性,从而影响我们与对象属性的交互方式。简单来说,writable 控制属性值是否可被重新赋值,而 enumerable 则控制属性是否能在某些遍历操作(比如 for...in 循环或 Object.keys())中被发现。理解并恰当使用它们,能让我们对对象的行为有更精细的掌控。

解决方案

在 JavaScript 中,每个对象属性都有一个与之关联的属性描述符(Property Descriptor)。这些描述符定义了属性的各种特性,其中 writableenumerable 是我们经常需要打交道的两个。它们通过 Object.defineProperty() 方法来设置,或者通过 Object.getOwnPropertyDescriptor() 来查看。

writable 特性:控制属性值是否可写

当一个属性的 writable 特性被设置为 false 时,意味着这个属性的值不能通过简单的赋值操作被改变。尝试去修改它,在非严格模式下会静默失败(即不报错但修改无效),在严格模式下则会抛出 TypeError 错误。这对于我们希望保护某些配置、常量或者内部状态不被意外修改的场景非常有用。

考虑这个例子:

const config = {};

Object.defineProperty(config, 'API_KEY', {
    value: 'some_secret_key_123',
    writable: false, // 不可写
    enumerable: true, // 可枚举,方便查看
    configurable: false // 不可配置,更严格
});

console.log(config.API_KEY); // 输出: some_secret_key_123

// 尝试修改不可写的属性
config.API_KEY = 'new_key'; // 在非严格模式下静默失败,严格模式下会报错

console.log(config.API_KEY); // 仍然输出: some_secret_key_123

// 如果是严格模式,会报错:
// "use strict";
// config.API_KEY = 'new_key'; // TypeError: Cannot assign to read only property 'API_KEY' of object '#<Object>'

在我自己的开发经验里,writable: false 经常用于定义一些全局的、不可变的环境变量或者模块内部的固定配置。它提供了一种比 const 声明更底层的不可变性控制,因为 const 只是保证变量绑定不被改变,而 writable: false 是针对对象属性值本身的。

enumerable 特性:控制属性是否可枚举

enumerable 特性决定了属性是否会在某些遍历操作中出现。当 enumerable 设置为 false 时,这个属性将不会被 for...in 循环、Object.keys()Object.values()Object.entries() 以及 JSON.stringify() 等方法识别和包含。但请注意,属性依然可以通过直接访问(例如 obj.propertyName)来获取其值。

这在很多场景下都非常有用,比如我们想在对象中存储一些内部的、不希望暴露给外部迭代或序列化的辅助属性。

const user = {
    name: 'Alice',
    age: 30
};

Object.defineProperty(user, 'internalId', {
    value: 'user_xyz_123',
    writable: false,
    enumerable: false, // 不可枚举
    configurable: false
});

console.log(user.internalId); // 可以直接访问:user_xyz_123

// 遍历对象属性
for (let key in user) {
    console.log(key); // 只输出: name, age
}

console.log(Object.keys(user)); // 输出: ['name', 'age']
console.log(JSON.stringify(user)); // 输出: {"name":"Alice","age":30}

我记得有一次在调试一个前端组件时,发现 JSON.stringify 出来的对象总是多了一些不该有的内部状态,排查后才发现是这些内部属性的 enumerable 特性没有被正确设置为 false。这让我意识到,在设计复杂对象时,对 enumerable 的精细控制是多么重要,它直接影响了数据的序列化和外部可见性。

默认行为的差异

值得一提的是,直接通过赋值语句创建的属性(例如 obj.prop = 'value')默认都是 writable: true, enumerable: true, configurable: true。而通过 Object.defineProperty() 创建属性时,如果没有明确指定这些描述符,它们的默认值则都是 false。这是一个常见的陷阱,很多人会因此感到困惑,为什么 defineProperty 后属性变得不可写或不可枚举了。

const myObj = {};

myObj.a = 1; // 默认 writable: true, enumerable: true, configurable: true

Object.defineProperty(myObj, 'b', {
    value: 2 // 没有指定 writable, enumerable, configurable
});
// 此时 'b' 默认是 writable: false, enumerable: false, configurable: false

console.log(Object.getOwnPropertyDescriptor(myObj, 'a'));
// { value: 1, writable: true, enumerable: true, configurable: true }
console.log(Object.getOwnPropertyDescriptor(myObj, 'b'));
// { value: 2, writable: false, enumerable: false, configurable: false }

这个默认行为的差异,在我看来,是 JavaScript 在设计时的一种权衡。直接赋值是为了快速便捷,而 defineProperty 则更多是为了精确控制,所以默认值更偏向于“安全”和“限制”。

为什么我们需要精确控制 JavaScript 对象的属性行为?

精确控制 JavaScript 对象的属性行为,远不止是语法上的一个技巧,它在实际开发中扮演着至关重要的角色。我个人认为,这主要关乎代码的健壮性、安全性、可维护性以及在特定场景下的性能考量。

首先,防止意外修改是核心原因之一。想象一下,你正在开发一个复杂的库或者框架,其中有一些内部状态或者配置项,它们在初始化后不应该被外部代码随意篡改。如果这些属性是 writable: true,那么任何地方的代码都可以不经意间改变它们,这可能导致难以追踪的 bug,甚至破坏整个系统的稳定性。通过将这些关键属性设置为 writable: false,我们就像给它们加了一把锁,确保了其值的完整性。这在构建不可变数据结构或者实现单例模式时尤其有用。

其次,控制数据的可见性和暴露程度。不是所有的对象属性都应该在每次遍历或序列化时被展示出来。例如,一个用户对象可能包含 nameemail 等公共信息,但也可能包含 passwordHashsessionToken 等敏感信息,或者 _internalCounter_cache 等内部辅助属性。如果这些内部或敏感属性都是 enumerable: true,那么当我们将对象打印到控制台、通过 JSON.stringify 发送到后端,或者简单地用 for...in 循环时,它们就会暴露出来。这不仅可能造成安全隐患,也使得对象结构显得臃肿,增加了调试的复杂性。通过设置 enumerable: false,我们可以有效地“隐藏”这些属性,让对象的公共接口更加清晰,同时保护了内部实现细节。

再者,这种控制有助于提升代码的可读性和意图表达。当其他开发者(或者未来的你自己)看到一个属性被定义为不可写或不可枚举时,他们会立即明白这个属性的特殊性。这是一种非常明确的编程意图表达,减少了误解和误用。它告诉我们:“这个属性是设计来这样使用的,不要尝试去改变它,也不要期望它会出现在遍历中。”

最后,虽然不是最主要的原因,但在某些极端性能敏感的场景下,减少不必要的枚举操作也可能有微小的性能益处。但这通常不是我们考虑 enumerable 的首要驱动力,更重要的是逻辑和结构上的清晰。

writable 和 enumerable 默认值是什么?如何改变它们?

理解 writableenumerable 的默认值以及如何修改它们,是掌握属性描述符的关键一步。这其中存在一个非常重要的区别,取决于你创建属性的方式。

默认值:取决于属性的创建方式

  1. 通过直接赋值创建的属性: 当你像这样创建一个属性时:

    const myObject = {};
    myObject.myProp = 'Hello';

    这个 myProp 属性的 writableenumerableconfigurable 都会被默认设置为 true。这意味着 myProp 的值可以被修改,它会在 for...in 循环和 Object.keys() 中出现,并且其属性描述符本身也可以被修改(例如,你可以再次使用 Object.defineProperty 来改变它的 writable 状态)。

    Kacha
    Kacha

    KaCha是一款革命性的AI写真工具,用AI技术将照片变成杰作!

    下载
  2. 通过 Object.defineProperty() 创建的属性: 如果你使用 Object.defineProperty() 来创建属性,但没有明确指定 writableenumerableconfigurable,那么它们的默认值会是 false。这是一个非常常见的“陷阱”,很多人会在这里感到困惑。

    const myObject = {};
    Object.defineProperty(myObject, 'anotherProp', {
        value: 'World' // 没有指定 writable, enumerable, configurable
    });

    在这种情况下,anotherPropwritableenumerableconfigurable 都会是 false。这意味着它将是不可写的、不可枚举的,并且其描述符也无法再被修改。

我个人在初学 JavaScript 时,就曾因为 Object.defineProperty 的这个默认行为而感到困惑。当时我以为只要设置了 value,其他特性都会像普通属性一样是 true,结果发现属性无法修改也无法遍历,花了一段时间才搞清楚这个默认值的差异。

如何改变它们?

改变 writableenumerable 的唯一官方方式就是使用 Object.defineProperty() 方法。这个方法允许你精确地定义或修改一个对象上属性的特性。

const product = {
    id: 'p101',
    name: 'Laptop'
};

// 假设我们想让 id 属性不可写
Object.defineProperty(product, 'id', {
    writable: false // 将 id 设置为不可写
});

product.id = 'p102'; // 尝试修改,在非严格模式下静默失败
console.log(product.id); // 输出: p101

// 假设我们想添加一个不可枚举的内部序列号
Object.defineProperty(product, 'serialNumber', {
    value: 'SN-XYZ-456',
    writable: false,
    enumerable: false // 设置为不可枚举
});

console.log(product.serialNumber); // 可以直接访问: SN-XYZ-456
console.log(Object.keys(product)); // 输出: ['id', 'name'] - serialNumber 不在其中

需要注意的是,Object.defineProperty() 只能修改 configurable: true 的属性的描述符。如果一个属性的 configurable 已经是 false,那么你就无法再通过 Object.defineProperty() 来改变它的 writableenumerable 状态了(除了将 writabletrue 改为 false,这是一个例外)。这是一个更深层次的控制,通常用于创建非常“锁定”的属性。

除了 Object.defineProperty(),还有 Object.defineProperties() 方法,它允许你一次性为对象定义多个属性及其描述符,这在初始化一个包含多个特殊属性的对象时非常方便。

在实际开发中,writable 和 enumerable 有哪些高级应用场景?

writableenumerable 不仅仅是控制属性行为的基础工具,在一些更复杂的开发场景中,它们能够发挥出意想不到的威力,帮助我们构建更健壮、更灵活、更安全的系统。

1. 构建不可变配置或常量模块

在大型应用中,我们经常需要定义一些全局的、在运行时不应被修改的配置项或常量。直接使用 const 声明固然可以防止变量重新赋值,但如果 const 引用的是一个对象,对象的属性仍然是可变的。这时,writable: false 就派上了用场。

// constants.js
const CONFIG = {};

Object.defineProperties(CONFIG, {
    API_BASE_URL: {
        value: 'https://api.example.com/v1',
        writable: false,
        enumerable: true, // 方便查看
        configurable: false
    },
    TIMEOUT_MS: {
        value: 5000,
        writable: false,
        enumerable: true,
        configurable: false
    }
});

// 甚至可以进一步冻结整个对象,防止添加新属性或删除现有属性
Object.freeze(CONFIG);

export default CONFIG;

// 在其他模块使用
import CONFIG from './constants.js';
console.log(CONFIG.API_BASE_URL); // https://api.example.com/v1
// CONFIG.API_BASE_URL = 'new_url'; // 严格模式下会报错,非严格模式下静默失败

通过这种方式,我们创建了一个真正意义上的不可变配置对象,任何试图修改其属性的行为都会被阻止,大大增强了代码的可靠性。

2. 实现“私有”或内部辅助属性

尽管 JavaScript 没有真正的私有属性(直到 ES2022 的 # 私有字段),但 enumerable: false 提供了一种模拟“私有”行为的有效方式,特别是对于那些不希望在外部被轻易发现或序列化的内部状态。

比如,在一个类或模块中,你可能需要一些只供内部逻辑使用的缓存、计数器或状态标识。

class DataStore {
    constructor() {
        this.data = [];
        Object.defineProperty(this, '_lastFetchedTimestamp', {
            value: Date.now(),
            writable: true, // 内部可修改
            enumerable: false, // 外部不可见
            configurable: false
        });
        Object.defineProperty(this, '_updateCount', {
            value: 0,
            writable: true,
            enumerable: false,
            configurable: false
        });
    }

    addData(item) {
        this.data.push(item);
        this._updateCount++; // 内部修改
        this._lastFetchedTimestamp = Date.now();
    }

    toJSON() {
        // 当序列化时,_lastFetchedTimestamp 和 _updateCount 不会被包含
        return {
            data: this.data
        };
    }
}

const store = new DataStore();
store.addData('item1');
store.addData('item2');

console.log(store._updateCount); // 仍然可以直接访问:2
console.log(Object.keys(store)); // 输出: ['data']
console.log(JSON.stringify(store)); // 输出: {"data":["item1","item2"]}

这种模式在库和框架的内部实现中非常常见,它允许开发者在对象上存储必要的内部数据,同时保持公共 API 的整洁和清晰。

3. 数据序列化控制

enumerable: false 在数据序列化,特别是与 JSON.stringify() 配合时,表现得尤为强大。我们经常需要将 JavaScript 对象转换为 JSON 字符串进行网络传输或本地存储。如果对象中包含循环引用、函数、Symbol 或其他不适合 JSON 格式的数据,或者如前面提到的敏感信息,enumerable: false 可以帮助我们精确控制哪些属性应该被序列化。

const userProfile = {
    username: 'john_doe',
    email: 'john@example.com',
    passwordHash: 'some_hash_value', // 敏感信息
    getDisplayName() { return this.username; } // 函数
};

Object.defineProperty(userProfile, 'passwordHash', {
    enumerable: false // 不希望被序列化
});
Object.defineProperty(userProfile, 'getDisplayName', {
    enumerable: false // 函数默认就不会被 JSON.stringify 序列化,但明确设置可以增强意图
});

console.log(JSON.stringify(userProfile));
// 输出: {"username":"john_doe","email":"john@example.com"}
// passwordHash 和 getDisplayName 都没有被包含

这种精细控制避免了在序列化时手动筛选属性的繁琐,使得数据传输更加安全和高效。

4. 框架和库的 API 设计

在设计复杂的 JavaScript 库或框架时,开发者经常会利用这些特性来构建更健壮和用户友好的 API。例如,一个库可能在返回给用户的对象上添加一些内部方法或属性,但又不希望这些方法或属性污染用户的 for...in 循环,或者被意外修改。

// 假设这是一个库内部的辅助函数
function createObservable(initialValue) {
    let value = initialValue;
    const subscribers = [];

    const observable = {
        get value() { return value; },
        set value(newValue) {
            value = newValue;
            subscribers.forEach(cb => cb(value));
        }
    };

    // 添加一个内部的订阅方法,不希望它被枚举
    Object.defineProperty(observable, '_subscribe', {
        value: (callback) => subscribers.push(callback),
        writable: false,
        enumerable: false, // 不可枚举
        configurable: false
    });

    return observable;
}

const myObs = createObservable(10);
myObs._subscribe(val => console.log('New value:', val)); // 内部使用

console.log(Object.keys(myObs)); // 输出: ['value']
myObs.value = 20; // 输出: New value: 20

这里,_subscribe 方法是 createObservable 内部机制的一部分,通过 enumerable: false,它不会在 Object.keys() 中出现,使得 observable 对象的公共接口看起来更简洁。

这些高级应用场景展示了 writableenumerable 不仅仅是简单的开关,它们是构建复杂、安全和可维护 JavaScript 应用程序的强大基石。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

455

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

546

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

334

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

82

2025.09.10

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1566

2023.10.24

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

562

2023.09.20

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

760

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

221

2023.09.04

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

3

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 10.5万人学习

Swoft2.x速学之http api篇课程
Swoft2.x速学之http api篇课程

共16课时 | 1万人学习

PHP基础入门课程
PHP基础入门课程

共33课时 | 2.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号