docs/beyond/concepts/object-methods.mdx
How do you loop through an object's properties? How do you transform an object's keys? Or create a true copy of an object without unexpected side effects?
JavaScript's Object constructor comes with a powerful toolkit of static methods that let you inspect, iterate, transform, and clone objects. Once you know them, you'll reach for them constantly.
const user = { name: 'Alice', age: 30, city: 'NYC' }
// Get all keys, values, or key-value pairs
Object.keys(user) // ['name', 'age', 'city']
Object.values(user) // ['Alice', 30, 'NYC']
Object.entries(user) // [['name', 'Alice'], ['age', 30], ['city', 'NYC']]
// Transform and rebuild
const upperKeys = Object.fromEntries(
Object.entries(user).map(([key, value]) => [key.toUpperCase(), value])
)
// { NAME: 'Alice', AGE: 30, CITY: 'NYC' }
Object methods are static functions on JavaScript's built-in Object constructor that let you inspect, manipulate, and transform objects. Unlike instance methods you call on the object itself (like toString()), these are called on Object directly with the target object passed as an argument. According to MDN, the Object constructor provides over 30 static methods, with new ones like Object.groupBy() added as recently as ES2024.
const product = { name: 'Laptop', price: 999 }
// Static method: called on Object
Object.keys(product) // ['name', 'price']
// Instance method: called on the object
product.toString() // '[object Object]'
Think of Object as a toolbox sitting next to your workbench. You don't modify the toolbox itself. You reach into it, grab a tool, and use it on whatever object you're working with.
Imagine you have a filing cabinet (your object) and a set of tools for working with it:
┌─────────────────────────────────────────────────────────────────────────┐
│ THE OBJECT TOOLBOX │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ YOUR OBJECT (Filing Cabinet) THE TOOLS (Object.*) │
│ ┌─────────────────────┐ ┌────────────────────────────┐ │
│ │ name: "Alice" │ │ keys() → list labels │ │
│ │ age: 30 │ ────► │ values() → list contents │ │
│ │ city: "NYC" │ │ entries() → list both │ │
│ └─────────────────────┘ │ assign() → copy/merge │ │
│ │ hasOwn() → check exists │ │
│ │ groupBy() → organize │ │
│ └────────────────────────────┘ │
│ │
│ You don't modify the toolbox. You use the tools ON your object. │
│ │
└─────────────────────────────────────────────────────────────────────────┘
These three methods convert an object into an array you can loop over or transform.
Returns an array of the object's own enumerable property names (keys).
const user = { name: 'Alice', age: 30, city: 'NYC' }
const keys = Object.keys(user)
console.log(keys) // ['name', 'age', 'city']
// Loop through keys
for (const key of Object.keys(user)) {
console.log(key) // 'name', 'age', 'city'
}
Returns an array of the object's own enumerable property values.
const user = { name: 'Alice', age: 30, city: 'NYC' }
const values = Object.values(user)
console.log(values) // ['Alice', 30, 'NYC']
// Sum all numeric values
const scores = { math: 95, science: 88, history: 92 }
const total = Object.values(scores).reduce((sum, score) => sum + score, 0)
console.log(total) // 275
Returns an array of [key, value] pairs. This is the most versatile of the three.
const user = { name: 'Alice', age: 30, city: 'NYC' }
const entries = Object.entries(user)
console.log(entries)
// [['name', 'Alice'], ['age', 30], ['city', 'NYC']]
// Destructure in a loop
for (const [key, value] of Object.entries(user)) {
console.log(`${key}: ${value}`)
}
// name: Alice
// age: 30
// city: NYC
| Method | Returns | Use When |
|---|---|---|
Object.keys(obj) | ['key1', 'key2', ...] | You only need the property names |
Object.values(obj) | [value1, value2, ...] | You only need the values |
Object.entries(obj) | [['key1', value1], ...] | You need both keys and values |
Object.fromEntries() is the inverse of Object.entries(). It takes an iterable of [key, value] pairs and builds an object.
const entries = [['name', 'Alice'], ['age', 30]]
const user = Object.fromEntries(entries)
console.log(user) // { name: 'Alice', age: 30 }
The real power comes from combining entries() and fromEntries() with array methods like map() and filter().
const user = { name: 'Alice', age: 30, city: 'NYC' }
// Convert all keys to uppercase
const upperCased = Object.fromEntries(
Object.entries(user).map(([key, value]) => [key.toUpperCase(), value])
)
console.log(upperCased) // { NAME: 'Alice', AGE: 30, CITY: 'NYC' }
const product = { name: 'Laptop', price: 999, inStock: true, sku: 'LP001' }
// Keep only string values
const stringsOnly = Object.fromEntries(
Object.entries(product).filter(([key, value]) => typeof value === 'string')
)
console.log(stringsOnly) // { name: 'Laptop', sku: 'LP001' }
const map = new Map([
['name', 'Alice'],
['role', 'Admin']
])
const obj = Object.fromEntries(map)
console.log(obj) // { name: 'Alice', role: 'Admin' }
JavaScript objects are assigned by reference. When you need a separate copy, you have several options.
Object.assign() copies all enumerable own properties from source objects to a target object.
const target = { a: 1 }
const source = { b: 2 }
Object.assign(target, source)
console.log(target) // { a: 1, b: 2 }
For cloning, use an empty object as the target:
const original = { name: 'Alice', age: 30 }
const clone = Object.assign({}, original)
clone.name = 'Bob'
console.log(original.name) // 'Alice' — original unchanged
Merge multiple objects:
const defaults = { theme: 'light', fontSize: 14 }
const userPrefs = { theme: 'dark' }
const settings = Object.assign({}, defaults, userPrefs)
console.log(settings) // { theme: 'dark', fontSize: 14 }
const original = {
name: 'Alice',
address: { city: 'NYC' }
}
const clone = Object.assign({}, original)
clone.address.city = 'LA'
console.log(original.address.city) // 'LA' — both changed!
structuredClone() creates a true deep copy, including nested objects. It was added to browsers and Node.js in 2022. As the web.dev team documented, structuredClone() replaced the common JSON.parse(JSON.stringify(obj)) workaround that failed with Date, Map, Set, RegExp, and circular references.
const original = {
name: 'Alice',
address: { city: 'NYC' }
}
const clone = structuredClone(original)
clone.address.city = 'LA'
console.log(original.address.city) // 'NYC' — original unchanged!
It also handles:
const data = {
date: new Date('2024-01-01'),
items: new Set([1, 2, 3])
}
const clone = structuredClone(data)
console.log(clone.date instanceof Date) // true
console.log(clone.items instanceof Set) // true
const obj = {
greet: () => 'Hello' // Function
}
structuredClone(obj) // Throws: DataCloneError
| Method | Depth | Speed | Use When |
|---|---|---|---|
Object.assign() | Shallow | Fast | Merging objects, no nested objects |
Spread {...obj} | Shallow | Fast | Quick clone, no nested objects |
structuredClone() | Deep | Slower | Nested objects that must be independent |
Object.hasOwn() checks if an object has a property as its own (not inherited). It's the modern replacement for hasOwnProperty(), introduced in ES2022. MDN recommends using Object.hasOwn() over Object.prototype.hasOwnProperty() in all new code because it works correctly with null-prototype objects and cannot be overridden.
const user = { name: 'Alice', age: 30 }
console.log(Object.hasOwn(user, 'name')) // true
console.log(Object.hasOwn(user, 'toString')) // false (inherited)
console.log(Object.hasOwn(user, 'email')) // false (doesn't exist)
Object.hasOwn() is safer in two situations:
1. Objects with null prototype:
const nullProto = Object.create(null)
nullProto.id = 1
// hasOwnProperty doesn't exist on null-prototype objects!
nullProto.hasOwnProperty('id') // TypeError!
// Object.hasOwn works fine
Object.hasOwn(nullProto, 'id') // true
2. Objects that override hasOwnProperty:
const sneaky = {
hasOwnProperty: () => false // Someone overrode it!
}
sneaky.hasOwnProperty('hasOwnProperty') // false (wrong!)
Object.hasOwn(sneaky, 'hasOwnProperty') // true (correct!)
Object.is() compares two values for same-value equality. It's like === but handles two edge cases differently.
// Same as ===
Object.is(5, 5) // true
Object.is('hello', 'hello') // true
Object.is({}, {}) // false (different references)
// Different from ===
Object.is(NaN, NaN) // true (=== returns false!)
Object.is(0, -0) // false (=== returns true!)
You rarely need it, but it's essential when:
NaN values (though Number.isNaN() is usually clearer)+0 from -0 (rare mathematical scenarios)// NaN comparison
const value = NaN
value === NaN // false (always!)
Object.is(value, NaN) // true
Number.isNaN(value) // true (preferred for this case)
// Zero comparison
const positiveZero = 0
const negativeZero = -0
positiveZero === negativeZero // true
Object.is(positiveZero, negativeZero) // false
Object.groupBy() groups array elements by the result of a callback function. It's brand new in ES2024.
const inventory = [
{ name: 'apples', type: 'fruit', quantity: 5 },
{ name: 'bananas', type: 'fruit', quantity: 3 },
{ name: 'carrots', type: 'vegetable', quantity: 10 },
{ name: 'broccoli', type: 'vegetable', quantity: 7 }
]
const byType = Object.groupBy(inventory, item => item.type)
console.log(byType)
// {
// fruit: [
// { name: 'apples', type: 'fruit', quantity: 5 },
// { name: 'bananas', type: 'fruit', quantity: 3 }
// ],
// vegetable: [
// { name: 'carrots', type: 'vegetable', quantity: 10 },
// { name: 'broccoli', type: 'vegetable', quantity: 7 }
// ]
// }
The callback can return any string to use as the group key:
const products = [
{ name: 'Laptop', price: 999 },
{ name: 'Mouse', price: 29 },
{ name: 'Monitor', price: 399 },
{ name: 'Keyboard', price: 89 }
]
const byPriceRange = Object.groupBy(products, product => {
if (product.price < 50) return 'budget'
if (product.price < 200) return 'mid-range'
return 'premium'
})
console.log(byPriceRange)
// {
// premium: [{ name: 'Laptop', price: 999 }, { name: 'Monitor', price: 399 }],
// budget: [{ name: 'Mouse', price: 29 }],
// 'mid-range': [{ name: 'Keyboard', price: 89 }]
// }
These methods reveal more details about an object's properties.
Returns all own property names, including non-enumerable ones:
const arr = [1, 2, 3]
Object.keys(arr) // ['0', '1', '2']
Object.getOwnPropertyNames(arr) // ['0', '1', '2', 'length']
Returns all own Symbol-keyed properties:
const id = Symbol('id')
const obj = {
name: 'Alice',
[id]: 12345
}
Object.keys(obj) // ['name']
Object.getOwnPropertySymbols(obj) // [Symbol(id)]
For controlling what can be done to an object, see Property Descriptors. Here's a quick reference:
| Method | Add Properties | Delete Properties | Modify Values |
|---|---|---|---|
| Normal object | Yes | Yes | Yes |
Object.preventExtensions() | No | Yes | Yes |
Object.seal() | No | No | Yes |
Object.freeze() | No | No | No |
const config = { apiUrl: 'https://api.example.com' }
Object.freeze(config)
config.apiUrl = 'https://evil.com' // Silently fails
console.log(config.apiUrl) // 'https://api.example.com'
For creating objects with a specific prototype, see Object Creation & Prototypes. Brief example:
const personProto = {
greet() { return `Hi, I'm ${this.name}` }
}
const alice = Object.create(personProto)
alice.name = 'Alice'
console.log(alice.greet()) // "Hi, I'm Alice"
Common in React/Redux for transforming state:
// Normalize an API response into a lookup object
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]
const usersById = Object.fromEntries(
users.map(user => [user.id, user])
)
// { 1: { id: 1, name: 'Alice' }, 2: { id: 2, name: 'Bob' } }
Common in libraries and frameworks:
function createClient(userOptions = {}) {
const defaults = {
timeout: 5000,
retries: 3,
baseUrl: 'https://api.example.com'
}
const options = Object.assign({}, defaults, userOptions)
// ... use options
}
function processData(data) {
if (Object.hasOwn(data, 'userId')) {
// Safe to use data.userId
}
}
Object.keys(), values(), entries() — Convert objects to arrays for iteration and transformation.
Object.fromEntries() — Builds an object from key-value pairs. Combine with entries() for object transformations.
Object.assign() is shallow — Only the top level is copied. Nested objects are still shared references.
structuredClone() is deep — Creates a true independent copy, including nested objects.
Object.hasOwn() beats hasOwnProperty() — Works on null-prototype objects and can't be overridden.
Object.is() handles NaN and -0 — Use it when strict equality (===) isn't enough.
Object.groupBy() is ES2024 — Check browser support before using without a polyfill.
These are static methods — Called as Object.method(obj), not obj.method().
Only own enumerable properties — keys(), values(), and entries() skip inherited and non-enumerable properties.
Spread {...obj} is just shallow — Same as Object.assign({}, obj).
`Object.keys()` returns only **enumerable** own properties.
`Object.getOwnPropertyNames()` returns **all** own properties, including non-enumerable ones.
```javascript
const arr = [1, 2, 3]
Object.keys(arr) // ['0', '1', '2']
Object.getOwnPropertyNames(arr) // ['0', '1', '2', 'length']
// 'length' is non-enumerable
```
Use `structuredClone()` for a true deep copy:
```javascript
const original = {
user: { name: 'Alice' }
}
const clone = structuredClone(original)
clone.user.name = 'Bob'
console.log(original.user.name) // 'Alice' — unchanged
```
Note: `structuredClone()` can't clone functions or DOM nodes.
`===` follows IEEE 754 floating-point rules where NaN is not equal to anything, including itself. This is technically correct for numeric comparison but often counterintuitive.
`Object.is()` uses "same-value equality" which treats NaN as equal to NaN, matching what most developers expect.
```javascript
NaN === NaN // false (IEEE 754 rule)
Object.is(NaN, NaN) // true (same-value equality)
```
Two reasons:
1. **Null-prototype objects** don't have `hasOwnProperty`:
```javascript
const obj = Object.create(null)
obj.hasOwnProperty('key') // TypeError!
Object.hasOwn(obj, 'key') // Works fine
```
2. **Objects can override hasOwnProperty**:
```javascript
const obj = { hasOwnProperty: () => false }
obj.hasOwnProperty('hasOwnProperty') // false (wrong!)
Object.hasOwn(obj, 'hasOwnProperty') // true (correct!)
```
Use `Object.entries()`, `map()`, and `Object.fromEntries()`:
```javascript
const obj = { name: 'Alice', age: 30 }
const upperKeys = Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key.toUpperCase(), value])
)
console.log(upperKeys) // { NAME: 'Alice', AGE: 30 }
```
`Object.groupBy()` returns a null-prototype object where each property is an array of elements that match that group key. It was added in ES2024 (March 2024).
```javascript
const items = [
{ type: 'fruit', name: 'apple' },
{ type: 'fruit', name: 'banana' },
{ type: 'veggie', name: 'carrot' }
]
const grouped = Object.groupBy(items, item => item.type)
// {
// fruit: [{ type: 'fruit', name: 'apple' }, { type: 'fruit', name: 'banana' }],
// veggie: [{ type: 'veggie', name: 'carrot' }]
// }
```