对象方法
概述
JavaScript 提供了丰富的对象方法,用于操作、检查、转换和保护对象。这些方法主要分为:
- 静态方法:通过
Object构造函数调用的方法(如Object.keys()) - 实例方法:通过对象实例调用的方法(如
obj.hasOwnProperty())
掌握这些方法对于高效操作对象至关重要。
静态方法
属性访问与遍历
Object.keys()
返回对象自身可枚举属性名组成的数组。
const obj = { a: 1, b: 2, c: 3 }
console.log(Object.keys(obj)) // ['a', 'b', 'c']
// 只返回自有可枚举属性
const obj2 = Object.create({ inherited: true })
obj2.own = 'property'
console.log(Object.keys(obj2)) // ['own'](不包含继承属性)
// 数组的 keys
const arr = ['a', 'b', 'c']
console.log(Object.keys(arr)) // ['0', '1', '2']
// 空对象
console.log(Object.keys({})) // []特性:
- 只返回字符串属性(Symbol 属性被忽略)
- 只返回可枚举属性
- 只返回自有属性(不含原型链)
- 属性顺序:数字键升序 → 字符串键按添加顺序
Object.values()
返回对象自身可枚举属性值组成的数组。
const obj = { a: 1, b: 2, c: 3 }
console.log(Object.values(obj)) // [1, 2, 3]
// 只返回自有可枚举属性的值
const obj2 = Object.create({ inherited: 'value' })
obj2.own = 'property'
console.log(Object.values(obj2)) // ['property']
// 结合解构使用
const { length } = Object.values(obj)
console.log(length) // 3Object.entries()
返回对象自身可枚举属性的键值对数组。
const obj = { a: 1, b: 2, c: 3 }
console.log(Object.entries(obj)) // [['a', 1], ['b', 2], ['c', 3]]
// 遍历对象
for (const [key, value] of Object.entries(obj)) {
console.log(`${key}: ${value}`)
}
// 转换对象(键大写,值翻倍)
const transformed = Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key.toUpperCase(), value * 2])
)
console.log(transformed) // { A: 2, B: 4, C: 6 }
// 过滤属性
const filtered = Object.fromEntries(
Object.entries(obj).filter(([key, value]) => value > 1)
)
console.log(filtered) // { b: 2, c: 3 }Object.fromEntries()
将键值对列表转换为对象(ES2019)。
// 从 entries 创建对象
const entries = [['name', 'John'], ['age', 30]]
const obj = Object.fromEntries(entries)
console.log(obj) // { name: 'John', age: 30 }
// 从 Map 转换
const map = new Map([['name', 'John'], ['age', 30]])
const objFromMap = Object.fromEntries(map)
console.log(objFromMap) // { name: 'John', age: 30 }
// 对象转换示例:属性名大写
const obj1 = { a: 1, b: 2 }
const upper = Object.fromEntries(
Object.entries(obj1).map(([k, v]) => [k.toUpperCase(), v])
)
console.log(upper) // { A: 1, B: 2 }Object.getOwnPropertyNames()
返回对象自身所有字符串属性名(包括不可枚举属性)。
const obj = { a: 1, b: 2 }
Object.defineProperty(obj, 'secret', {
value: 'hidden',
enumerable: false
})
console.log(Object.keys(obj)) // ['a', 'b']
console.log(Object.getOwnPropertyNames(obj)) // ['a', 'b', 'secret']对比 Object.keys():
Object.keys():只返回可枚举属性Object.getOwnPropertyNames():返回所有字符串属性(包括不可枚举)
Object.getOwnPropertySymbols()
返回对象自身所有 Symbol 属性组成的数组。
const sym1 = Symbol('id')
const sym2 = Symbol('name')
const obj = {
[sym1]: 123,
[sym2]: 'John',
age: 30
}
console.log(Object.keys(obj)) // ['age']
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(id), Symbol(name)]
// 获取 Symbol 属性值
obj[Object.getOwnPropertySymbols(obj)[0]] // 123Reflect.ownKeys()
返回对象自身所有属性键(字符串 + Symbol,包括不可枚举)。
const obj = {
name: 'John',
[Symbol('id')]: 123
}
Object.defineProperty(obj, 'age', { value: 30, enumerable: false })
console.log(Reflect.ownKeys(obj)) // ['name', 'age', Symbol(id)]
// 等价于
const allKeys = [
...Object.getOwnPropertyNames(obj),
...Object.getOwnPropertySymbols(obj)
]属性遍历方法对比表:
| 方法 | 自有属性 | 可枚举 | Symbol | 不可枚举 | 原型链 |
|---|---|---|---|---|---|
Object.keys() | ✓ | ✓ | ✗ | ✗ | ✗ |
Object.values() | ✓ | ✓ | ✗ | ✗ | ✗ |
Object.entries() | ✓ | ✓ | ✗ | ✗ | ✗ |
Object.getOwnPropertyNames() | ✓ | - | ✗ | ✓ | ✗ |
Object.getOwnPropertySymbols() | ✓ | - | ✓ | ✓ | ✗ |
Reflect.ownKeys() | ✓ | - | ✓ | ✓ | ✗ |
for...in | ✓ | ✓ | ✗ | ✗ | ✓ |
属性定义与修改
Object.defineProperty()
定义或修改对象的单个属性,可精确控制属性特性。
const obj = {}
// 定义数据属性
Object.defineProperty(obj, 'name', {
value: 'John',
writable: false, // 不可写
enumerable: true, // 可枚举
configurable: true // 可配置
})
obj.name = 'Jane' // 无效(严格模式报错)
console.log(obj.name) // 'John'
// 定义访问器属性
let _age = 0
Object.defineProperty(obj, 'age', {
get() {
console.log('读取 age')
return _age
},
set(value) {
console.log('设置 age:', value)
_age = value
},
enumerable: true,
configurable: true
})
obj.age = 30 // 设置 age: 30
console.log(obj.age) // 读取 age \n 30属性描述符默认值:
// 使用 defineProperty 时,默认值为 false
Object.defineProperty(obj, 'key', {})
// 等价于
Object.defineProperty(obj, 'key', {
value: undefined,
writable: false,
enumerable: false,
configurable: false
})
// 使用字面量创建时,默认值为 true
const obj2 = { key: 'value' }
// 等价于
// { value: 'value', writable: true, enumerable: true, configurable: true }Object.defineProperties()
一次性定义或修改多个属性。
const obj = {}
Object.defineProperties(obj, {
name: {
value: 'John',
writable: true,
enumerable: true
},
age: {
value: 30,
writable: false,
enumerable: true
},
info: {
get() {
return `${this.name}, ${this.age} years old`
},
enumerable: true
}
})
console.log(obj.name) // 'John'
console.log(obj.info) // 'John, 30 years old'Object.getOwnPropertyDescriptor()
获取单个属性的描述符。
const obj = { name: 'John' }
const descriptor = Object.getOwnPropertyDescriptor(obj, 'name')
console.log(descriptor)
// {
// value: 'John',
// writable: true,
// enumerable: true,
// configurable: true
// }
// 访问器属性
Object.defineProperty(obj, 'fullName', {
get() { return 'John Doe' }
})
const accessorDesc = Object.getOwnPropertyDescriptor(obj, 'fullName')
console.log(accessorDesc)
// {
// get: [Function: get],
// set: undefined,
// enumerable: false,
// configurable: false
// }Object.getOwnPropertyDescriptors()
获取对象所有属性的描述符。
const obj = { name: 'John', age: 30 }
const descriptors = Object.getOwnPropertyDescriptors(obj)
console.log(descriptors)
// {
// name: { value: 'John', writable: true, enumerable: true, configurable: true },
// age: { value: 30, writable: true, enumerable: true, configurable: true }
// }
// 用于精确复制对象(包括 getter/setter)
const clone = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)对象创建与原型
Object.create()
创建新对象,指定其原型对象和属性。
// 创建指定原型的对象
const proto = {
sayHello() {
console.log('Hello!')
},
greet() {
console.log('Hi, I am ' + this.name)
}
}
const obj = Object.create(proto)
obj.name = 'John'
// ... 中间省略 ...
console.log(pureObj.toString) // undefined(无继承方法)
console.log(pureObj instanceof Object) // false
// 创建普通对象(默认原型)
const normalObj = Object.create(Object.prototype)
// 等价于 const normalObj = {}使用场景:
- 实现继承
- 创建纯净对象(避免原型链污染)
- 精确控制对象原型
Object.getPrototypeOf()
获取对象的原型。
const obj = {}
console.log(Object.getPrototypeOf(obj) === Object.prototype) // true
const arr = []
console.log(Object.getPrototypeOf(arr) === Array.prototype) // true
const obj2 = Object.create(null)
console.log(Object.getPrototypeOf(obj2)) // null
// 获取构造函数的原型
function Person(name) {
this.name = name
}
const person = new Person('John')
console.log(Object.getPrototypeOf(person) === Person.prototype) // trueObject.setPrototypeOf()
设置对象的原型(不推荐,性能较差)。
const obj = {}
const proto = {
sayHello() {
console.log('Hello!')
}
}
Object.setPrototypeOf(obj, proto)
obj.sayHello() // 'Hello!'
console.log(Object.getPrototypeOf(obj) === proto) // true性能警告:
Object.setPrototypeOf()会影响性能,不推荐在生产环境使用- 建议使用
Object.create()创建新对象
// ❌ 不推荐
const obj = {}
Object.setPrototypeOf(obj, proto)
// ✅ 推荐
const obj = Object.create(proto)对象复制与合并
Object.assign()
将源对象的可枚举自有属性复制到目标对象。
// 合并对象
const target = { a: 1 }
const source = { b: 2, c: 3 }
const result = Object.assign(target, source)
console.log(result) // { a: 1, b: 2, c: 3 }
console.log(result === target) // true(修改原对象)
// 浅拷贝
const original = { a: 1, b: { c: 2 } }
const copy = Object.assign({}, original)
copy.b.c = 3
console.log(original.b.c) // 3(浅拷贝,嵌套对象引用相同)
// 合并多个对象
const merged = Object.assign({}, { a: 1 }, { b: 2 }, { c: 3 })
console.log(merged) // { a: 1, b: 2, c: 3 }
// 属性覆盖(后者覆盖前者)
const obj1 = { a: 1, b: 2 }
const obj2 = { b: 3, c: 4 }
const result = Object.assign({}, obj1, obj2)
console.log(result) // { a: 1, b: 3, c: 4 }特性:
- 修改目标对象(第一个参数)
- 只复制可枚举的自有属性
- 浅拷贝(嵌套对象引用相同)
Symbol属性也会被复制
扩展运算符(...)
ES2018 引入的对象扩展运算符,更简洁的合并方式。
// 浅拷贝
const obj = { a: 1, b: 2 }
const copy = { ...obj }
console.log(copy) // { a: 1, b: 2 }
console.log(copy === obj) // false(创建新对象)
// 合并对象
const obj1 = { a: 1 }
const obj2 = { b: 2 }
const merged = { ...obj1, ...obj2 }
console.log(merged) // { a: 1, b: 2 }
// 覆盖属性
const obj3 = { a: 1, b: 2 }
const result = { ...obj3, b: 3, c: 4 }
console.log(result) // { a: 1, b: 3, c: 4 }
// 条件属性
const hasPermission = true
const config = {
name: 'John',
...(hasPermission && { role: 'admin' })
}
console.log(config) // { name: 'John', role: 'admin' }
// 移除属性
const { age, ...rest } = { name: 'John', age: 30, city: 'NYC' }
console.log(rest) // { name: 'John', city: 'NYC' }Object.assign vs 扩展运算符:
const obj = { a: 1, b: 2 }
// Object.assign 修改目标对象
const result1 = Object.assign(obj, { c: 3 })
console.log(obj === result1) // true(obj 被修改)
// 扩展运算符创建新对象
const result2 = { ...obj, c: 3 }
console.log(obj === result2) // false(obj 未被修改)推荐:优先使用扩展运算符,语义更清晰且不修改原对象。
对象保护
Object.freeze()
完全冻结对象,防止任何修改。
const obj = { name: 'John', age: 30 }
Object.freeze(obj)
// 所有修改操作都无效
obj.name = 'Jane' // 无效
delete obj.age // 无效
obj.city = 'NYC' // 无效
console.log(obj) // { name: 'John', age: 30 }
console.log(Object.isFrozen(obj)) // true
// ... 中间省略 ...
return obj
}
const obj3 = { a: { b: { c: 1 } } }
deepFreeze(obj3)
obj3.a.b.c = 2 // 无效Object.isFrozen()
检查对象是否被冻结。
const obj = { name: 'John' }
console.log(Object.isFrozen(obj)) // false
Object.freeze(obj)
console.log(Object.isFrozen(obj)) // true
// 冻结的对象也是密封和不可扩展的
console.log(Object.isSealed(obj)) // true
console.log(Object.isExtensible(obj)) // falseObject.seal()
密封对象,防止添加/删除属性,但允许修改现有属性。
const obj = { name: 'John', age: 30 }
Object.seal(obj)
obj.name = 'Jane' // 有效(可修改)
delete obj.age // 无效(不可删除)
obj.city = 'NYC' // 无效(不可添加)
console.log(obj) // { name: 'Jane', age: 30 }
console.log(Object.isSealed(obj)) // true
// 密封对象不可扩展
console.log(Object.isExtensible(obj)) // falseObject.isSealed()
检查对象是否被密封。
const obj = { name: 'John' }
console.log(Object.isSealed(obj)) // false
Object.seal(obj)
console.log(Object.isSealed(obj)) // trueObject.preventExtensions()
阻止对象扩展,禁止添加新属性,但允许修改和删除。
const obj = { name: 'John' }
Object.preventExtensions(obj)
obj.age = 30 // 无效(不可添加)
obj.name = 'Jane' // 有效(可修改)
delete obj.name // 有效(可删除)
console.log(obj) // {}
console.log(Object.isExtensible(obj)) // falseObject.isExtensible()
检查对象是否可扩展。
const obj = { name: 'John' }
console.log(Object.isExtensible(obj)) // true
Object.preventExtensions(obj)
console.log(Object.isExtensible(obj)) // false
// 冻结和密封的对象也是不可扩展的
Object.freeze({})
console.log(Object.isExtensible({})) // false(已冻结)对象保护方法对比
| 方法 | 添加属性 | 删除属性 | 修改属性值 | 修改属性描述符 | 检查方法 |
|---|---|---|---|---|---|
| 无保护 | ✓ | ✓ | ✓ | ✓ | - |
preventExtensions() | ✗ | ✓ | ✓ | ✓ | Object.isExtensible() |
seal() | ✗ | ✗ | ✓ | ✗ | Object.isSealed() |
freeze() | ✗ | ✗ | ✗ | ✗ | Object.isFrozen() |
层层递进:freeze() 最严格,preventExtensions() 最宽松。
// 关系:freeze ⊂ seal ⊂ preventExtensions
// freeze 包含 seal 和 preventExtensions 的限制
Object.freeze(obj)
console.log(Object.isSealed(obj)) // true
console.log(Object.isExtensible(obj)) // false
// seal 包含 preventExtensions 的限制
Object.seal(obj)
console.log(Object.isExtensible(obj)) // false
console.log(Object.isFrozen(obj)) // false比较与判断
Object.is()
精确比较两个值是否相同。
// 与 === 的区别
console.log(NaN === NaN) // false
console.log(Object.is(NaN, NaN)) // true
console.log(-0 === 0) // true
console.log(Object.is(-0, 0)) // false
// 其他情况与 === 相同
console.log(Object.is('foo', 'foo')) // true
console.log(Object.is({}, {})) // false(不同引用)
console.log(Object.is(null, null)) // true
console.log(Object.is(undefined, undefined)) // true
// 对象比较(引用比较)
const obj = { a: 1 }
console.log(Object.is(obj, obj)) // true
console.log(Object.is(obj, { a: 1 })) // falseObject.is vs ===:
| 值 | === | Object.is() |
|---|---|---|
NaN vs NaN | false | true |
-0 vs 0 | true | false |
| 其他情况 | 相同 | 相同 |
Object.hasOwn()
检查对象是否有指定的自有属性(ES2022,推荐)。
const obj = { name: 'John' }
console.log(Object.hasOwn(obj, 'name')) // true
console.log(Object.hasOwn(obj, 'toString')) // false(继承属性)
// 比 hasOwnProperty 更安全
const obj2 = Object.create(null) // 无原型对象
obj2.name = 'John'
// obj2.hasOwnProperty('name') // TypeError: obj2.hasOwnProperty is not a function
console.log(Object.hasOwn(obj2, 'name')) // true
// 属性值为 undefined 时仍返回 true
obj2.age = undefined
console.log(Object.hasOwn(obj2, 'age')) // true推荐使用 Object.hasOwn() 替代 hasOwnProperty()。
实例方法
属性检查
hasOwnProperty()
检查对象是否有指定的自有属性。
const obj = { name: 'John' }
console.log(obj.hasOwnProperty('name')) // true
console.log(obj.hasOwnProperty('toString')) // false(继承属性)
// 注意:可能被覆盖
const obj2 = {
name: 'John',
hasOwnProperty: () => false
}
console.log(obj2.hasOwnProperty('name')) // false(被覆盖)
// 安全调用方式
console.log(Object.prototype.hasOwnProperty.call(obj2, 'name')) // true
// 或使用 Object.hasOwn()(推荐)
console.log(Object.hasOwn(obj2, 'name')) // truepropertyIsEnumerable()
检查属性是否可枚举。
const obj = { name: 'John' }
Object.defineProperty(obj, 'age', {
value: 30,
enumerable: false
})
console.log(obj.propertyIsEnumerable('name')) // true
console.log(obj.propertyIsEnumerable('age')) // false
// 继承属性
console.log(obj.propertyIsEnumerable('toString')) // falseisPrototypeOf()
检查对象是否在另一个对象的原型链上。
const proto = { sayHello() { console.log('Hello!') } }
const obj = Object.create(proto)
console.log(proto.isPrototypeOf(obj)) // true
console.log(Object.prototype.isPrototypeOf(obj)) // true
// 检查原型链
function Animal() {}
function Dog() {}
Dog.prototype = Object.create(Animal.prototype)
const dog = new Dog()
console.log(Animal.prototype.isPrototypeOf(dog)) // true
console.log(Object.prototype.isPrototypeOf(dog)) // true类型转换
toString()
返回对象的字符串表示。
const obj = { name: 'John' }
console.log(obj.toString()) // '[object Object]'
// 自定义 toString
const person = {
name: 'John',
age: 30,
toString() {
return `Person: ${this.name}, ${this.age} years old`
}
}
// ... 中间省略 ...
return Object.prototype.toString.call(value).slice(8, -1)
}
console.log(getType([])) // 'Array'
console.log(getType(null)) // 'Null'
console.log(getType(new Map())) // 'Map'toLocaleString()
返回对象的本地化字符串表示。
const obj = { name: 'John', age: 30 }
console.log(obj.toLocaleString()) // '[object Object]'
// 数组的本地化
const arr = [1000, 2000, 3000]
console.log(arr.toLocaleString()) // '1,000,2,000,3,000'(根据地区)
// 日期的本地化
const date = new Date('2024-01-01')
console.log(date.toLocaleString()) // '2024/1/1 00:00:00'(根据地区)
console.log(date.toLocaleDateString()) // '2024/1/1'
console.log(date.toLocaleTimeString()) // '00:00:00'
// 数字格式化
const num = 1234567.89
console.log(num.toLocaleString()) // '1,234,567.89'
console.log(num.toLocaleString('zh-CN', { style: 'currency', currency: 'CNY' }))
// '¥1,234,567.89'valueOf()
返回对象的原始值。
const obj = { name: 'John' }
console.log(obj.valueOf()) // { name: 'John' }(返回对象本身)
// 自定义 valueOf
const obj2 = {
value: 10,
valueOf() {
return this.value
}
}
console.log(obj2 + 5) // 15(自动调用 valueOf)
// 结合 toString
const obj3 = {
value: 10,
valueOf() {
return this.value
},
toString() {
return `Value: ${this.value}`
}
}
console.log(obj3 + 5) // 15(数字上下文,优先 valueOf)
console.log(String(obj3)) // 'Value: 10'(字符串上下文,优先 toString)类型转换优先级:
| 上下文 | Hint | 优先级 |
|---|---|---|
| 数字运算 | number | valueOf → toString |
| 字符串转换 | string | toString → valueOf |
其他(如 +) | default | valueOf → toString |
实用工具函数
对象映射
// 映射对象的值
function mapObject(obj, mapper) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key, mapper(value, key)])
)
}
const obj = { a: 1, b: 2, c: 3 }
const doubled = mapObject(obj, value => value * 2)
console.log(doubled) // { a: 2, b: 4, c: 6 }
const withPrefix = mapObject(obj, (value, key) => `${key}-${value}`)
console.log(withPrefix) // { a: 'a-1', b: 'b-2', c: 'c-3' }对象过滤
// 过滤对象的属性
function filterObject(obj, predicate) {
return Object.fromEntries(
Object.entries(obj).filter(([key, value]) => predicate(value, key))
)
}
const obj = { a: 1, b: 2, c: 3, d: 4 }
const evens = filterObject(obj, value => value % 2 === 0)
console.log(evens) // { b: 2, d: 4 }
// 过滤掉特定键
const excludeKeys = (obj, keys) =>
filterObject(obj, (_, key) => !keys.includes(key))
const filtered = excludeKeys(obj, ['a', 'c'])
console.log(filtered) // { b: 2, d: 4 }对象合并
// 浅合并
function mergeObjects(...objects) {
return Object.assign({}, ...objects)
}
const obj1 = { a: 1 }
const obj2 = { b: 2 }
const obj3 = { c: 3 }
console.log(mergeObjects(obj1, obj2, obj3)) // { a: 1, b: 2, c: 3 }
// 深度合并
// ... 中间省略 ...
}
const obj1 = { a: { b: 1, c: 2 } }
const obj2 = { a: { c: 3, d: 4 }, e: 5 }
const merged = deepMerge({}, obj1, obj2)
console.log(merged) // { a: { b: 1, c: 3, d: 4 }, e: 5 }深度拷贝
// 简单深拷贝(JSON 方式,有局限)
function deepCloneSimple(obj) {
return JSON.parse(JSON.stringify(obj))
}
// 局限:无法处理函数、undefined、Symbol、循环引用、Date、RegExp 等
// 完整深拷贝
function deepClone(obj, hash = new WeakMap()) {
// null 或非对象类型
if (obj === null || typeof obj !== 'object') return obj
// 处理循环引用
// ... 中间省略 ...
}
// 使用 structuredClone(现代浏览器,推荐)
const obj = { a: 1, b: { c: 2 }, date: new Date() }
const clone = structuredClone(obj)
console.log(clone) // { a: 1, b: { c: 2 }, date: Date }对象路径访问
// 根据路径获取值
function getValueByPath(obj, path, defaultValue = undefined) {
const keys = path.split('.')
let result = obj
for (const key of keys) {
if (result === null || result === undefined) {
return defaultValue
}
result = result[key]
}
// ... 中间省略 ...
target[lastKey] = value
return obj
}
setValueByPath(obj, 'user.profile.city', 'NYC')
console.log(obj.user.profile.city) // 'NYC'对象扁平化与还原
// 扁平化对象
function flatten(obj, prefix = '', result = {}) {
for (const [key, value] of Object.entries(obj)) {
const newKey = prefix ? `${prefix}.${key}` : key
if (value && typeof value === 'object' && !Array.isArray(value)) {
flatten(value, newKey, result)
} else {
result[newKey] = value
}
}
// ... 中间省略 ...
return result
}
const flat = { 'a.b.c': 1, 'a.b.d': 2, 'a.e': 3 }
console.log(unflatten(flat)) // { a: { b: { c: 1, d: 2 }, e: 3 } }最佳实践
1. 属性检查
// ❌ 避免
if (obj.property) { } // 值为 falsy 时误判
// ✅ 推荐
if ('property' in obj) { } // 检查原型链
if (obj.hasOwnProperty('property')) { } // 只检查自有属性
if (Object.hasOwn(obj, 'property')) { } // ES2022,推荐
// ✅ 安全访问深层属性
const value = obj?.nested?.property ?? 'default'2. 对象遍历
// ❌ 避免 for...in(会遍历原型链)
for (const key in obj) {
console.log(key)
}
// ✅ 使用 Object.keys()
Object.keys(obj).forEach(key => {
console.log(key, obj[key])
})
// ✅ 使用 Object.entries()
for (const [key, value] of Object.entries(obj)) {
console.log(key, value)
}
// ✅ 需要遍历包括不可枚举属性时
Reflect.ownKeys(obj).forEach(key => {
console.log(key, obj[key])
})3. 对象合并
// ❌ 修改原对象
Object.assign(obj, { a: 1 })
// ✅ 创建新对象(不可变)
const newObj = { ...obj, a: 1 }
// ✅ 深度合并使用库或自定义函数
// lodash.merge({}, obj1, obj2)4. 对象保护
// ✅ 配置对象使用 Object.freeze()
const CONFIG = Object.freeze({
API_URL: 'https://api.example.com',
TIMEOUT: 5000
})
// ✅ 类内部属性使用 Symbol 或 WeakMap
const privateData = new WeakMap()
class Person {
constructor(name, secret) {
this.name = name
privateData.set(this, { secret })
}
}5. 类型判断
// ❌ 避免使用 typeof 判断对象类型
typeof {} // 'object'
typeof [] // 'object'
typeof null // 'object'
typeof new Date() // 'object'
// ✅ 使用 Object.prototype.toString
function getType(value) {
return Object.prototype.toString.call(value).slice(8, -1)
}
getType({}) // 'Object'
getType([]) // 'Array'
getType(null) // 'Null'
getType(new Date()) // 'Date'
// ✅ 使用 Array.isArray 判断数组
Array.isArray([]) // true
Array.isArray({}) // false常见问题(FAQ)
Q1: Object.keys() 和 for...in 有什么区别?
const obj = { a: 1, b: 2 }
Object.setPrototypeOf(obj, { c: 3 })
// for...in 遍历原型链
for (const key in obj) {
console.log(key) // 'a', 'b', 'c'
}
// Object.keys() 只返回自有可枚举属性
console.log(Object.keys(obj)) // ['a', 'b']Q2: 如何判断对象是否为空?
const obj = {}
// 方法1:Object.keys()
console.log(Object.keys(obj).length === 0) // true
// 方法2:Reflect.ownKeys()(包含 Symbol)
console.log(Reflect.ownKeys(obj).length === 0) // true
// 方法3:for...in
function isEmpty(obj) {
for (const key in obj) {
if (Object.hasOwn(obj, key)) {
return false
}
}
return true
}
// 方法4:JSON.stringify()
console.log(JSON.stringify(obj) === '{}') // true(简单但有局限)Q3: 如何实现对象的深拷贝?
// 方法1:structuredClone(现代浏览器,推荐)
const clone = structuredClone(obj)
// 方法2:JSON(简单但有局限)
const clone = JSON.parse(JSON.stringify(obj))
// 方法3:递归实现(完整版见上文"深度拷贝"部分)
const clone = deepClone(obj)
// 方法4:使用库
// import { cloneDeep } from 'lodash'
// const clone = cloneDeep(obj)Q4: Object.assign 和扩展运算符有什么区别?
const obj = { a: 1 }
// Object.assign:修改目标对象
const result1 = Object.assign(obj, { b: 2 })
console.log(obj === result1) // true(obj 被修改)
// 扩展运算符:创建新对象
const result2 = { ...obj, c: 3 }
console.log(obj === result2) // false(obj 未被修改)
// 推荐使用扩展运算符,更符合函数式编程原则Q5: 如何合并两个对象并处理冲突?
const obj1 = { a: 1, b: 2 }
const obj2 = { b: 3, c: 4 }
// 简单合并(后者覆盖前者)
const merged = { ...obj1, ...obj2 }
console.log(merged) // { a: 1, b: 3, c: 4 }
// 自定义合并策略
function mergeWithStrategy(obj1, obj2, strategy) {
const result = { ...obj1 }
for (const [key, value] of Object.entries(obj2)) {
if (key in result) {
result[key] = strategy(result[key], value, key)
} else {
result[key] = value
}
}
return result
}
// 数组值合并
const obj3 = { tags: ['js'] }
const obj4 = { tags: ['ts'] }
const merged = mergeWithStrategy(obj3, obj4, (v1, v2) => [...v1, ...v2])
console.log(merged) // { tags: ['js', 'ts'] }Q6: 如何安全地访问深层嵌套的属性?
const user = {
profile: {
address: {
city: 'NYC'
}
}
}
// ❌ 传统方式(冗长)
const city = user && user.profile && user.profile.address && user.profile.address.city
// ✅ 可选链操作符(ES2020)
const city = user?.profile?.address?.city // 'NYC'
const country = user?.profile?.address?.country // undefined
// ✅ 结合空值合并运算符
const city = user?.profile?.address?.city ?? 'Unknown' // 'NYC'
// ✅ 使用工具函数(见上文"对象路径访问"部分)
const city = getValueByPath(user, 'profile.address.city', 'Unknown')