{T}

对象方法

概述

JavaScript 提供了丰富的对象方法,用于操作、检查、转换和保护对象。这些方法主要分为:

  • 静态方法:通过 Object 构造函数调用的方法(如 Object.keys()
  • 实例方法:通过对象实例调用的方法(如 obj.hasOwnProperty()

掌握这些方法对于高效操作对象至关重要。

静态方法

属性访问与遍历

Object.keys()

返回对象自身可枚举属性名组成的数组。

javascript
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()

返回对象自身可枚举属性值组成的数组。

javascript
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)  // 3

Object.entries()

返回对象自身可枚举属性的键值对数组。

javascript
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)。

javascript
// 从 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()

返回对象自身所有字符串属性名(包括不可枚举属性)。

javascript
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 属性组成的数组。

javascript
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]]  // 123

Reflect.ownKeys()

返回对象自身所有属性键(字符串 + Symbol,包括不可枚举)。

javascript
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()

定义或修改对象的单个属性,可精确控制属性特性。

javascript
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

属性描述符默认值

javascript
// 使用 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()

一次性定义或修改多个属性。

javascript
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()

获取单个属性的描述符。

javascript
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()

获取对象所有属性的描述符。

javascript
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()

创建新对象,指定其原型对象和属性。

javascript
// 创建指定原型的对象
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()

获取对象的原型。

javascript
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)  // true

Object.setPrototypeOf()

设置对象的原型(不推荐,性能较差)。

javascript
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() 创建新对象
javascript
// ❌ 不推荐
const obj = {}
Object.setPrototypeOf(obj, proto)

// ✅ 推荐
const obj = Object.create(proto)

对象复制与合并

Object.assign()

将源对象的可枚举自有属性复制到目标对象。

javascript
// 合并对象
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 引入的对象扩展运算符,更简洁的合并方式。

javascript
// 浅拷贝
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 扩展运算符

javascript
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()

完全冻结对象,防止任何修改。

javascript
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()

检查对象是否被冻结。

javascript
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))     // false

Object.seal()

密封对象,防止添加/删除属性,但允许修改现有属性。

javascript
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))  // false

Object.isSealed()

检查对象是否被密封。

javascript
const obj = { name: 'John' }
console.log(Object.isSealed(obj))  // false

Object.seal(obj)
console.log(Object.isSealed(obj))  // true

Object.preventExtensions()

阻止对象扩展,禁止添加新属性,但允许修改和删除。

javascript
const obj = { name: 'John' }

Object.preventExtensions(obj)

obj.age = 30       // 无效(不可添加)
obj.name = 'Jane'  // 有效(可修改)
delete obj.name    // 有效(可删除)

console.log(obj)  // {}
console.log(Object.isExtensible(obj))  // false

Object.isExtensible()

检查对象是否可扩展。

javascript
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() 最宽松。

javascript
// 关系: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()

精确比较两个值是否相同。

javascript
// 与 === 的区别
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 }))  // false

Object.is vs ===

===Object.is()
NaN vs NaNfalsetrue
-0 vs 0truefalse
其他情况相同相同

Object.hasOwn()

检查对象是否有指定的自有属性(ES2022,推荐)。

javascript
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()

检查对象是否有指定的自有属性。

javascript
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'))  // true

propertyIsEnumerable()

检查属性是否可枚举。

javascript
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'))  // false

isPrototypeOf()

检查对象是否在另一个对象的原型链上。

javascript
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()

返回对象的字符串表示。

javascript
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()

返回对象的本地化字符串表示。

javascript
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()

返回对象的原始值。

javascript
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优先级
数字运算numbervalueOf → toString
字符串转换stringtoString → valueOf
其他(如 +defaultvalueOf → toString

实用工具函数

对象映射

javascript
// 映射对象的值
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' }

对象过滤

javascript
// 过滤对象的属性
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 }

对象合并

javascript
// 浅合并
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 }

深度拷贝

javascript
// 简单深拷贝(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 }

对象路径访问

javascript
// 根据路径获取值
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'

对象扁平化与还原

javascript
// 扁平化对象
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. 属性检查

javascript
// ❌ 避免
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. 对象遍历

javascript
// ❌ 避免 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. 对象合并

javascript
// ❌ 修改原对象
Object.assign(obj, { a: 1 })

// ✅ 创建新对象(不可变)
const newObj = { ...obj, a: 1 }

// ✅ 深度合并使用库或自定义函数
// lodash.merge({}, obj1, obj2)

4. 对象保护

javascript
// ✅ 配置对象使用 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. 类型判断

javascript
// ❌ 避免使用 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 有什么区别?

javascript
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: 如何判断对象是否为空?

javascript
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: 如何实现对象的深拷贝?

javascript
// 方法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 和扩展运算符有什么区别?

javascript
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: 如何合并两个对象并处理冲突?

javascript
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: 如何安全地访问深层嵌套的属性?

javascript
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')

参考资料