知蛛网
  • 首页
  • 会员
  • 活动
  • 工作台

常见问题
活动
关于我们
联系我们
隐私协议
用户协议
会员协议
© 2026 知蛛网. 保留所有权利。
首页会员工作台我的
flynn@flynn
主页wiki简历作品集
目录
全部书籍
00-路线
  • 学习路线
  • 命名与写作规范
01-计算机基础
01-计算机网络
  • 互联网是如何工作的
  • HTTP协议
  • TCP/IP协议
  • 浏览器直接打开本地html文件,请求时的协议问题
02-浏览器原理
  • 浏览器是如何渲染页面的
  • 浏览器是如何渲染页面的
  • 面试之浏览器
03-操作系统
  • 进程与线程
  • 内存管理与虚拟内存
  • IO 模型与多路复用
  • 文件系统与页缓存
04-数据结构与算法
  • 数组与链表
  • 栈与队列
  • 哈希表
  • 树与堆
  • 基础数据结构
01-基础数据结构
02-高级算法
  • 排序算法
  • 二分查找
  • 递归与回溯
  • 动态规划
  • 双指针与滑动窗口
03-leetcode刷题
02-前端核心
01-web开发
  • Web 和 Web 标准
  • web 入门
  • web 开发
  • URL
  • 域名
  • Web服务器
  • 命令行
  • 工作流程和流程
  • 超链接
  • 浏览器指纹
02-html
  • HTML基础语法
  • HTML 概述
  • HTML 发展史
  • HTML调试
  • HTML标题与段落
  • 文本格式化
  • 强调与重要性
  • 块级元素和内联元素
  • 列表
  • 区块
  • 语义元素
  • 页面结构化和语义化
  • 超链接
  • 相对路径和绝对路径
  • 图片
  • 表格
  • 表单和按钮
  • 多媒体
  • 视频/音频
  • HTML 头部
  • HTML 字符集
  • HTML颜色
  • HTML 语言代码
  • HTML 国家/地区
  • Emoji (表情符号)
  • HTML 标签简写及全称 |
  • SVG
  • Canvas 2D
  • Canvas 3D
  • 框架 `<iframe>`
  • HTML 拖放
  • HTML5 Geolocation(地理定位)
  • Web 存储
  • HTML URL 编码
  • MathML
  • 远程图片资源在自己网站中无法展示问题
03-css
  • CSS简介
  • CSS 语法
  • CSS 创建
  • CSS 发展史
  • CSS 浏览器支持
  • CSS 选择器
  • CSS `!important` 规则
  • css颜色
  • CSS 文本格式
  • CSS 盒子模型
  • CSS 尺寸
  • CSS 内边距
  • CSS 外边距
  • CSS 边框
  • CSS 轮廓
  • CSS 背景(background)
  • CSS 溢出
  • CSS 文档流
  • CSS 显示和可见性
  • CSS 浮动
  • CSS 定位
  • CSS 对齐方式
  • CSS 网页布局
  • CSS 多列布局(CSS Multi-column Layout)
  • CSS 透明度
  • CSS 响应式设计(Responsive Design)
  • CSS 媒体查询
  • CSS 列表
  • CSS 表格
  • CSS 表单
  • CSS 链接
  • CSS 用户界面
  • CSS 计数器
  • CSS 过渡
  • CSS 动画
  • CSS 2D 转换
  • CSS 3D 转换
  • CSS 变量(CSS Custom Properties)
  • CSS 原生嵌套(CSS Nesting)
  • CSS 函数(CSS Functions)
04-javascript
  • 什么是 JavaScript?
  • 编程范式(Programming Paradigm)
  • 什么是范式?
  • ECMAScript 规范
  • 什么是语法糖
  • 开发环境搭建
  • 什么是堆和栈?
  • 什么是执行上下文
  • JavaScript 垃圾回收机制
  • 作业队列与事件循环
  • 深拷贝和浅拷贝
  • 语法与类型
  • 字面量
  • 控制流与错误处理
  • 循环与迭代
  • 函数
  • 表达式与运算符
  • 数字和字符串
  • 表达日期与时间
  • 正则表达式
  • 索引集合类
  • 带键的集合
  • 对象
  • 类
  • Promise
  • async/await
  • 迭代器和生成器
  • 国际化
  • 模块
  • 资源管理
  • 属性的可枚举性和所有权
  • 闭包
  • 继承与原型链
  • 元编程
  • 内存管理
  • 内置对象
  • arguments 对象
  • BOM 对象(浏览器对象模型)
  • DOM 对象(文档对象模型)
  • bind、apply、call的区别
  • new 操作符
  • typeof 和 instanceof 的区别
  • HTML5 Web Workers
05-typescript
  • TypeScript 类型系统基础
  • 类型体操
03-框架与跨端
01-react
  • 开发环境搭建
  • JSX
  • 组件
  • 生命周期
  • Hooks
  • 组件通信
  • 状态管理
  • React 19 新特性
  • useCallback 闭包陷阱
  • 常用的性能优化操作
  • Firbe 架构
  • React 18 新特性
  • useRef
  • 基于umijs 4.0 搭建官网
  • react+vite本地开发,如何手机联调
  • React 实现谷歌登录
  • React 项目调起 WhatsApp
02-vue
  • vue 解决跨域请求
  • 在vue2使用TinyMCE富文本编辑器
  • 使用jszip 导出新闻
  • Vue 本地开发启用 https
  • 基于vue3+vite+ts+elementplus+pinia+axios搭建后台管理系统
03-react-native
  • iOS 开发环境(RN 0.72)
  • RN 原生模块(Native Module)
  • 查看Maven包的Java包名
  • 优化 APP 启动速度
  • 安全键盘
  • 启动图与 Icon 尺寸规范
  • RN如何生成/配置iOS的开发者证书和生产证书
  • 邓白氏编码
  • 微信开放平台配置Universal Link
  • 上架至Apple App Store
04-android
  • 开发环境搭建
  • Android项目目录结构
  • 认识Gradle
04-工程化
01-git
  • 如何解决已被git跟踪文件不在跟踪
  • git标签
  • Git报错: Failed to connect to github.com port 443 解决方案
  • Git 分支操作速查
02-构建工具
  • 构建工具原理与选型
02-webpack
  • webpack 核心概念与构建流程
  • webpack 性能优化
03-vite
  • vite 为什么快
  • vite 插件与生产构建
03-测试
  • 前端测试体系
04-部署
  • Nginx 反向代理解决跨域
  • SPA 刷新非首页报 404
05-docker
  • Docker 常用命令
  • docker-compose nginx配置ssl证书
  • Docker Desktop 升级后 docker-compose 命令找不到
06-性能优化
  • 前端性能优化体系
  • 背景 GIF 优化方案
07-微前端
  • 微前端实战(qiankun)
  • 微前端 · qiankun
01-qiankun
05-后端与数据
01-nodejs
  • Node 版本与依赖的 engines 冲突
  • Express 实现 Gmail 发信(邮箱验证码)
02-php
  • 浏览器保存新闻,发布到自有网站
  • [分布式] 浏览器保存新闻,发布到自有网站
  • 邮件发送,多账号轮询
  • nginx和php-fpm通信问题导致网站页面空白
03-python
  • macOS M1 芯片 如何安装 py 和 tkinter
  • tkinter 编写的程序如何打包成桌面软件
04-架构设计
  • 后台管理系统中角色管理、管理员管理、权限设置他们三者之间的关系
05-运维
  • redis启动失败
  • 实现自动备份宝塔项目及数据库到本地
  • 宝塔面板
01-宝塔
06-实战项目
  • 实战准备工作
02-项目记录
  • AI-Chat 模块技术文档
  • CRMChat 部署
07-面试专项
01-面试
  • 面试之自我介绍
  • CSS 面试
  • JS面试
  • React面试
  • Vue面试
  • RN 面试 · 题目清单
  • RN 面试 · 答案详解
  • RN 物流公司 · 题目清单
  • RN 物流公司 · 答案详解
  • Uniapp 面试
  • 带团队经验
12-react专项
  • 一个表格,某一行的某一列数据变动了,如何优化性能?
13-reactnative专项
  • RN 开发过程中,有遇到什么棘手的问题?你是如何解决的?
14-过往面试总结
  • 冷哥传媒 · 面试题
  • 沐腾科技 · 面试题
  • 字符频率排序
02-面试八股
  • 网络八股
  • 浏览器八股
  • 工程化八股
03-算法面试题
  • 前端高频手写题
  • 场景设计题
04-真实面试经历
  • 20251027 盾蓝科技 面试
  • 20251028 芝麻甜心 面试
08-工具
01-n8n
  • 初始n8n
  • 快速入门
  • 在宝塔中搭建n8n环境
  • n8n中常用节点
  • n8n和代码的区别
02-Trae
  • Figma MCP 配置指南
03-UI_UX_pro_max
  • UI_UX_pro_max的使用
04-开发环境
  • VMWare虚拟机安装
  • PowerShell 执行策略与编码设置
98-杂项
  • A股股票交易相关费用
99-手册
  • iOS 开发者账号申请流程及费用
  • 邓白氏编码申请及费用
  • App 推送通道申请及费用
  • App 开发收费项目清单
  • 注册AWS账号
  • 使用Amazon Polly实现文本转语音
  • Google 翻译 Key 申请与前端调用
  • 使用SmsForwarder转发短信

属性的可枚举性和所有权

04-javascript2026/11/14

基本概念

1. 属性描述符(Property Descriptor)

每个属性都有对应的属性描述符,控制属性的行为:

const obj = { name: "John" };

// 获取属性描述符
const descriptor = Object.getOwnPropertyDescriptor(obj, "name");
console.log(descriptor);
// {
//   value: 'John',
//   writable: true,     // 可修改
//   enumerable: true,   // 可枚举
//   configurable: true  // 可配置
// }

2. 可枚举性(Enumerable)

  • 决定属性是否会在某些操作中出现
  • 默认为 true

3. 所有权(Ownership)

  • 自有属性:直接定义在对象上的属性
  • 继承属性:从原型链继承的属性

枚举相关的方法

1. 只遍历可枚举属性

const obj = {
    a: 1,
    b: 2,
};

// 定义不可枚举属性
Object.defineProperty(obj, "c", {
    value: 3,
    enumerable: false,
});

// 1. for...in (包含继承的可枚举属性)
console.log("for...in:");
for (let key in obj) {
    console.log(key); // a, b (没有c)
}

// 2. Object.keys() (自有可枚举属性)
console.log("Object.keys:", Object.keys(obj)); // ['a', 'b']

// 3. Object.values() (自有可枚举属性的值)
console.log("Object.values:", Object.values(obj)); // [1, 2]

// 4. Object.entries() (自有可枚举属性的键值对)
console.log("Object.entries:", Object.entries(obj)); // [['a', 1], ['b', 2]]

2. 包含不可枚举属性的遍历

const obj = {
    a: 1,
    b: 2,
};

Object.defineProperties(obj, {
    c: { value: 3, enumerable: false },
    d: { value: 4, enumerable: false },
});

// 1. Object.getOwnPropertyNames() (所有自有属性,无论是否可枚举)
console.log("getOwnPropertyNames:", Object.getOwnPropertyNames(obj)); // ['a', 'b', 'c', 'd']

// 2. Object.getOwnPropertySymbols() (所有自有Symbol属性)
const sym = Symbol("private");
obj[sym] = "secret";
console.log("getOwnPropertySymbols:", Object.getOwnPropertySymbols(obj)); // [Symbol(private)]

// 3. Reflect.ownKeys() (所有自有键,包括Symbol)
console.log("Reflect.ownKeys:", Reflect.ownKeys(obj)); // ['a', 'b', 'c', 'd', Symbol(private)]

3. 继承属性的影响

function Person(name) {
    this.name = name;
}

// 在原型上定义方法
Person.prototype.sayHello = function () {
    return `Hello, I'm ${this.name}`;
};

const john = new Person("John");

// 定义自有不可枚举属性
Object.defineProperty(john, "id", {
    value: 123,
    enumerable: false,
});

console.log("for...in (包含继承):");
for (let key in john) {
    console.log(key); // name, sayHello (没有id)
}

console.log("Object.keys (自有可枚举):", Object.keys(john)); // ['name']

console.log("getOwnPropertyNames (自有所有):", Object.getOwnPropertyNames(john)); // ['name', 'id']

属性描述符详解

1. 完整属性描述符

const obj = {};

// 定义完整属性描述符
Object.defineProperty(obj, "fullName", {
    // 数据描述符
    value: "John Doe",
    writable: true, // 可修改
    enumerable: true, // 可枚举
    configurable: true, // 可配置(可删除、可修改属性描述符)

    // 访问器描述符(不能与数据描述符同时存在)
    // get: function() { return this._fullName; },
    // set: function(value) { this._fullName = value; }
});

// 批量定义属性
Object.defineProperties(obj, {
    age: {
        value: 30,
        writable: false, // 不可修改(类似const)
        enumerable: true,
        configurable: true,
    },
    salary: {
        value: 50000,
        enumerable: false, // 不可枚举(私有属性效果)
        configurable: false, // 不可配置(锁死属性)
    },
});

obj.age = 40; // 静默失败(严格模式下会报错)
console.log(obj.age); // 30

delete obj.salary; // false(无法删除)
console.log(obj.salary); // 50000
上一篇资源管理
下一篇闭包
本页目录
基本概念1. 属性描述符(Property Descriptor)2. 可枚举性(Enumerable)3. 所有权(Ownership)枚举相关的方法1. 只遍历可枚举属性2. 包含不可枚举属性的遍历3. 继承属性的影响属性描述符详解1. 完整属性描述符2. 访问器属性(Getter/Setter)3. 属性描述符的继承性实践应用场景1. 实现真正的私有属性2. 不可变配置对象3. JSON 序列化控制4. 属性验证和监听性能考虑1. 枚举操作性能对比2. 属性访问优化特殊情况和陷阱1. 数组的特殊性2. Class 的默认行为3. Symbol 属性的枚举最佳实践

2. 访问器属性(Getter/Setter)

const user = {
    firstName: "John",
    lastName: "Doe",

    // 定义访问器属性
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    },

    set fullName(value) {
        const parts = value.split(" ");
        this.firstName = parts[0];
        this.lastName = parts[1] || "";
    },

    // 计算属性
    get age() {
        return new Date().getFullYear() - this.birthYear;
    },
};

user.birthYear = 1990;
console.log(user.fullName); // John Doe
console.log(user.age); // 计算出的年龄

user.fullName = "Jane Smith";
console.log(user.firstName); // Jane
console.log(user.lastName); // Smith

3. 属性描述符的继承性

class Animal {
    constructor(name) {
        this.name = name;
    }

    // 类方法是不可枚举的
    speak() {
        console.log(`${this.name} makes a noise.`);
    }
}

class Dog extends Animal {
    constructor(name) {
        super(name);
    }

    // 重写的方法也是不可枚举的
    speak() {
        console.log(`${this.name} barks.`);
    }
}

const dog = new Dog("Rex");

// 检查属性描述符
console.log(Object.getOwnPropertyDescriptor(dog, "name"));
// { value: 'Rex', writable: true, enumerable: true, configurable: true }

console.log(Object.getOwnPropertyDescriptor(Object.getPrototypeOf(dog), "speak"));
// { value: [Function: speak], writable: true, enumerable: false, configurable: true }

实践应用场景

1. 实现真正的私有属性

function createPrivateObject() {
    const privateData = new WeakMap();

    class PrivateObject {
        constructor() {
            // 存储私有数据
            privateData.set(this, {
                secret: Math.random(),
                count: 0,
            });

            // 定义不可枚举的访问器
            Object.defineProperty(this, "_internal", {
                enumerable: false,
                configurable: false,
                get() {
                    return privateData.get(this);
                },
            });
        }

        // 公有方法
        increment() {
            const data = privateData.get(this);
            data.count++;
            return data.count;
        }

        getSecret() {
            const data = privateData.get(this);
            return data.secret;
        }
    }

    return PrivateObject;
}

const PrivateObject = createPrivateObject();
const obj = new PrivateObject();

console.log(Object.keys(obj)); // []
console.log(obj.increment()); // 1
console.log(obj.getSecret()); // 随机数

2. 不可变配置对象

function createConfig(options = {}) {
    const config = {
        ...options,
        // 默认配置
        apiUrl: "https://api.example.com",
        timeout: 30000,
        retryCount: 3,
    };

    // 冻结所有属性
    Object.keys(config).forEach(key => {
        Object.defineProperty(config, key, {
            value: config[key],
            writable: false,
            enumerable: true,
            configurable: false,
        });
    });

    // 防止添加新属性
    Object.preventExtensions(config);

    return config;
}

const config = createConfig({ timeout: 5000 });

config.timeout = 10000; // 静默失败
config.newProp = "test"; // 静默失败
delete config.apiUrl; // false

console.log(Object.keys(config)); // ['apiUrl', 'timeout', 'retryCount']

3. JSON 序列化控制

class User {
    constructor(name, email, password) {
        this.name = name;
        this.email = email;

        // 密码设为不可枚举,不会被 JSON.stringify 包含
        Object.defineProperty(this, "password", {
            value: password,
            enumerable: false,
            writable: false,
            configurable: false,
        });

        // 内部状态设为不可枚举
        Object.defineProperty(this, "_metadata", {
            value: {
                created: new Date(),
                version: 1,
            },
            enumerable: false,
        });
    }

    toJSON() {
        // 自定义序列化
        return {
            name: this.name,
            email: this.email,
            // 不包含 password 和 _metadata
            metadata: this._metadata, // 需要时手动添加
        };
    }
}

const user = new User("John", "john@example.com", "secret123");

console.log("JSON.stringify:", JSON.stringify(user));
// {"name":"John","email":"john@example.com","metadata":{"created":"2024-01-01T00:00:00.000Z","version":1}}

console.log("Object.keys:", Object.keys(user)); // ['name', 'email']

4. 属性验证和监听

function createValidatedObject(schema) {
    const obj = {};

    for (const [key, validator] of Object.entries(schema)) {
        let value;

        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: false,

            get() {
                return value;
            },

            set(newValue) {
                // 验证
                if (!validator(newValue)) {
                    throw new Error(`Invalid value for ${key}: ${newValue}`);
                }

                // 通知变化
                if (obj._listeners && obj._listeners[key]) {
                    obj._listeners[key].forEach(callback => {
                        callback(newValue, value);
                    });
                }

                value = newValue;
            },
        });

        // 添加监听方法
        obj.onChange = function (prop, callback) {
            if (!this._listeners) {
                Object.defineProperty(this, "_listeners", {
                    value: {},
                    enumerable: false,
                });
            }

            if (!this._listeners[prop]) {
                this._listeners[prop] = [];
            }

            this._listeners[prop].push(callback);
        };
    }

    return obj;
}

// 使用示例
const schema = {
    age: value => Number.isInteger(value) && value >= 0,
    email: value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
    name: value => typeof value === "string" && value.length > 0,
};

const user = createValidatedObject(schema);

user.onChange("age", (newAge, oldAge) => {
    console.log(`Age changed from ${oldAge} to ${newAge}`);
});

user.age = 25; // OK
user.age = -5; // Error: Invalid value for age: -5

性能考虑

1. 枚举操作性能对比

const obj = {};
// 创建大量属性
for (let i = 0; i < 10000; i++) {
    obj[`prop${i}`] = i;
    // 每10个属性设一个不可枚举
    if (i % 10 === 0) {
        Object.defineProperty(obj, `hidden${i}`, {
            value: `hidden${i}`,
            enumerable: false,
        });
    }
}

// 性能测试
console.time("for...in");
for (let key in obj) {
}
console.timeEnd("for...in");

console.time("Object.keys");
Object.keys(obj);
console.timeEnd("Object.keys");

console.time("getOwnPropertyNames");
Object.getOwnPropertyNames(obj);
console.timeEnd("getOwnPropertyNames");

2. 属性访问优化

// 缓存可枚举属性列表
function getEnumerableKeys(obj) {
    if (!obj.__enumerableKeys) {
        Object.defineProperty(obj, "__enumerableKeys", {
            value: Object.keys(obj),
            enumerable: false,
            writable: true,
            configurable: true,
        });
    }
    return obj.__enumerableKeys;
}

// 当对象改变时更新缓存
function defineEnumerableProperty(obj, key, value) {
    Object.defineProperty(obj, key, {
        value,
        enumerable: true,
        writable: true,
        configurable: true,
    });

    // 更新缓存
    if (obj.__enumerableKeys) {
        if (!obj.__enumerableKeys.includes(key)) {
            obj.__enumerableKeys.push(key);
        }
    }
}

特殊情况和陷阱

1. 数组的特殊性

const arr = [1, 2, 3];

// 数组索引是可枚举的
console.log(Object.keys(arr)); // ['0', '1', '2']

// 添加不可枚举属性
Object.defineProperty(arr, "custom", {
    value: "hidden",
    enumerable: false,
});

// for...in 会包含索引
for (let key in arr) {
    console.log(key); // 0, 1, 2 (没有 custom)
}

// 但 length 是不可枚举的
console.log(Object.getOwnPropertyDescriptor(arr, "length"));
// { value: 3, writable: true, enumerable: false, configurable: false }

2. Class 的默认行为

class MyClass {
    instanceProp = "instance"; // 实例属性,可枚举

    constructor() {
        this.constructorProp = "constructor"; // 也是可枚举的
    }

    // 原型方法,不可枚举
    method() {}

    // 静态属性,可枚举
    static staticProp = "static";
}

const instance = new MyClass();

console.log(Object.keys(instance));
// ['instanceProp', 'constructorProp']

console.log(Object.getOwnPropertyNames(instance));
// ['instanceProp', 'constructorProp']

3. Symbol 属性的枚举

const obj = {
    regular: "regular",
    [Symbol("sym1")]: "symbol1",
    [Symbol("sym2")]: "symbol2",
};

// 定义不可枚举的Symbol
const sym3 = Symbol("sym3");
Object.defineProperty(obj, sym3, {
    value: "symbol3",
    enumerable: false,
});

console.log("Object.keys:", Object.keys(obj)); // ['regular']
console.log("for...in:"); // 只有 'regular'
console.log("getOwnPropertySymbols:", Object.getOwnPropertySymbols(obj)); // [Symbol(sym1), Symbol(sym2), Symbol(sym3)]
console.log("Reflect.ownKeys:", Reflect.ownKeys(obj)); // ['regular', Symbol(sym1), Symbol(sym2), Symbol(sym3)]

最佳实践

  1. 明确属性意图:
  • 公共 API:可枚举
  • 内部实现:不可枚举
  • 常量配置:writable: false
  1. 谨慎使用不可枚举属性:
  • 避免过度使用,会增加复杂性
  • 文档化不可枚举属性的存在
  1. 性能优化:
  • 频繁遍历的对象,保持属性可枚举
  • 大型对象,考虑缓存键列表
  1. 安全考虑:
  • 敏感数据设为不可枚举
  • 使用 Object.freeze() 或 Object.seal() 增强安全性
  1. 框架/库开发:
  • 原型方法设为不可枚举
  • 内部状态设为不可枚举
  • 提供明确的 API 访问方式
  1. 调试友好:
  • 开发环境下,可考虑让调试属性可枚举
  • 生产环境下,隐藏内部实现细节