A user_4a13383d 2026-09-08 09:40 3 次浏览 Javascript Object 常用属性与方法:从读取到保护的实战指南 ## 目录 - [一、先建立一张方法地图](#一先建立一张方法地图) - [二、读取对象:keys、values、entries](#二读取对象keysvaluesentries) - [三、转换对象:entries 与 fromEntries](#三转换对象entries-与-fromentries) - [四、复制与合并:assign 的边界](#四复制与合并assign-的边界) - [五、创建原型:create 和原型链](#五创建原型create-和原型链) - [六、定义属性:defineProperty 与描述符](#六定义属性defineproperty-与描述符) - [七、保护对象:freeze、seal 和 preventExtensions](#七保护对象freeze、seal-和-preventextensions) - [八、总结](#八总结) ## 一、先建立一张方法地图 | 需求 | 常用方法 | 关键提醒 | | --- | --- | --- | | 读取自有可枚举字符串键 | Object.keys | 看不到 Symbol 和不可枚举属性 | | 同时读取键和值 | Object.entries | 适合配合 for...of 和 map | | 读取自有全部字符串键 | Object.getOwnPropertyNames | 包含不可枚举属性 | | 合并一层配置 | Object.assign | 只做浅拷贝 | | 用指定原型创建对象 | Object.create | null 原型对象没有常规方法 | | 精确设置属性 | Object.defineProperty | 未填写的描述符默认是 false | | 查看属性规则 | Object.getOwnPropertyDescriptor | 不会沿原型链查找 | | 判断是否自有属性 | Object.hasOwn | 比直接调用 hasOwnProperty 稳 | | 防止对象继续变化 | Object.freeze / seal / preventExtensions | 都是浅层保护 | 下面用一个配置对象贯穿示例: ```javascript const defaults = { theme: 'light', pageSize: 20, features: { search: true, }, }; const userConfig = { pageSize: 50, }; ``` ## 二、读取对象:keys、values、entries ### Object.keys:只看自有可枚举字符串键 ```javascript const user = { name: 'Ada', role: 'admin' }; console.log(Object.keys(user)); // ['name', 'role'] console.log(Object.values(user)); // ['Ada', 'admin'] console.log(Object.entries(user)); // [['name', 'Ada'], ['role', 'admin']] ``` 这三个方法都只读取对象自己的可枚举字符串属性。原型上的属性、不可枚举属性和 Symbol 属性不会出现。 ### Object.entries 适合直接转换 ```javascript const labels = Object.entries(user).map(([key, value]) => { return key + ': ' + value; }); console.log(labels); // ['name: Ada', 'role: admin'] ``` 如果只需要键和值,不要先 keys 再手动取值;entries 一次就能把一对数据交给你。 ## 三、转换对象:entries 与 fromEntries Object.fromEntries 可以把键值对数组重新组装成对象,常用于过滤、重命名和转换: ```javascript const raw = { name: 'Ada', password: 'not-safe-to-display', role: 'admin', }; const publicUser = Object.fromEntries( Object.entries(raw).filter(([key]) => key !== 'password'), ); console.log(publicUser); // { name: 'Ada', role: 'admin' } ``` 这套 entries → 处理 → fromEntries 的流水线,比一边遍历一边修改原对象更容易读,也不容易留下半成品。 ## 四、复制与合并:assign 的边界 ### 先跑通主线 Object.assign 把后面对象的自有可枚举属性复制到第一个对象中: ```javascript const config = Object.assign({}, defaults, userConfig); console.log(config.pageSize); // 50 ``` 展开运算符的这一层效果相近: ```javascript const config = { ...defaults, ...userConfig }; ``` ### 边界来了:它只是浅拷贝 修改合并结果里的嵌套对象,会影响 defaults: ```javascript const config = { ...defaults, ...userConfig }; config.features.search = false; console.log(defaults.features.search); // false ``` 原因很简单:外层对象是新的,features 仍然是同一个引用。需要独立嵌套数据时,使用 structuredClone,或者根据业务需求显式复制: ```javascript const config = { ...defaults, ...userConfig, features: { ...defaults.features, ...userConfig.features, }, }; ``` 还要注意,Object.assign 是“写入目标对象”的操作。如果目标对象已有 setter,复制过程会触发 setter;如果目标属性是只读的,可能抛出异常或无法写入。只想生成新对象时,使用空对象作为第一个参数更稳妥。 ## 五、创建原型:create 和原型链 Object.create 用指定对象作为新对象的原型: ```javascript const accountMethods = { describe() { return this.name + '(' + this.role + ')'; }, }; const account = Object.create(accountMethods); account.name = 'Ada'; account.role = 'admin'; console.log(account.describe()); // Ada(admin) console.log(Object.hasOwn(account, 'describe')); // false console.log(Object.getPrototypeOf(account) === accountMethods); // true ``` describe 来自原型,不是 account 自己的属性。遍历或复制时要先决定是否需要继承属性。 ### null 原型对象 Object.create(null) 可以创建没有原型的“纯字典”: ```javascript const dictionary = Object.create(null); dictionary.name = 'Ada'; console.log(Object.getPrototypeOf(dictionary)); // null console.log(Object.hasOwn(dictionary, 'name')); // true ``` 它没有 toString、hasOwnProperty 等常规方法,适合存放外部输入的键,避免键名碰撞;使用时要记得通过 Object.hasOwn 判断属性。 ## 六、定义属性:defineProperty 与描述符 属性描述符决定一个属性能否被枚举、修改和删除: ```javascript const user = {}; Object.defineProperty(user, 'id', { value: 1001, enumerable: true, writable: false, configurable: false, }); console.log(user.id); // 1001 user.id = 2002; console.log(user.id); // 1001 console.log(Object.keys(user)); // ['id'] ``` 这段代码同时展示了几个属性描述符的作用: - `value: 1001` 指定属性的初始值。 - `enumerable: true` 让 `Object.keys` 能看到它。 - `writable: false` 禁止通过普通赋值改写它;非严格模式下赋值会被静默忽略,严格模式下可能抛出 `TypeError`。 - `configurable: false` 禁止删除属性或再次改变描述符。 因此 `user.id = 2002` 并不是把值改成了 `2002` 再被“恢复”,而是写入动作从一开始就不被允许。 ### 边界来了:没有填写的选项默认是 false ```javascript const settings = {}; Object.defineProperty(settings, 'theme', { value: 'dark', }); console.log(Object.keys(settings)); // [] settings.theme = 'light'; console.log(settings.theme); // dark ``` 这不是 defineProperty 失效,而是 enumerable、writable、configurable 没有填写时都默认为 false。需要普通可写属性时,显式写出配置,或者直接使用赋值语法。 ### 读取描述符 ```javascript const descriptor = Object.getOwnPropertyDescriptor(user, 'id'); console.log(descriptor); // { value: 1001, writable: false, enumerable: true, configurable: false } ``` 访问器属性使用 get 和 set: ```javascript const profile = { firstName: 'Ada', lastName: 'Lovelace', }; Object.defineProperty(profile, 'fullName', { enumerable: true, get() { return profile.firstName + ' ' + profile.lastName; }, }); console.log(profile.fullName); // Ada Lovelace ``` 读取 fullName 会执行 getter。遍历对象结构时,不要在不知情的情况下触发可能有副作用的 getter。 访问 `profile.fullName` 的过程是:先找到 `fullName` 的 getter,再执行 getter 函数;getter 内部读取 `firstName` 和 `lastName`,最后拼接出结果。它不是提前存好的普通值,所以如果修改 `profile.firstName`,下一次读取会得到新的姓名。 ## 七、保护对象:freeze、seal 和 preventExtensions 这三个方法都只处理对象自身的当前层,不会自动冻结嵌套对象: | 方法 | 能否新增属性 | 能否删除属性 | 能否修改已有值 | | --- | --- | --- | --- | | preventExtensions | 不允许 | 允许 | 允许 | | seal | 不允许 | 不允许 | 允许 | | freeze | 不允许 | 不允许 | 不允许 | ```javascript const state = { name: 'Ada', nested: { count: 1 }, }; Object.freeze(state); state.name = 'Grace'; state.nested.count = 2; console.log(state.name); // Ada console.log(state.nested.count); // 2,嵌套对象仍可修改 ``` 如果确实需要深层不可变,需要递归冻结,并处理循环引用;不要把 Object.freeze 当成深拷贝或深冻结。 ## 八、总结 - keys、values、entries 只读取自有可枚举字符串属性;需要完整键集合时考虑 getOwnPropertyNames 或 Reflect.ownKeys。 - assign 和展开运算符都只做浅拷贝,嵌套对象仍然共享引用。 - create 用来控制原型;null 原型对象适合字典,但没有常规对象方法。 - defineProperty 能精确控制属性规则,未填写的描述符默认是 false。 - Object.hasOwn 用于安全判断自有属性,不要依赖对象自己提供的 hasOwnProperty。 - preventExtensions、seal、freeze 都是浅层保护,使用前先确认需要的保护级别。 点赞0 收藏0
评论
还没有评论,来分享你的想法吧。