# Object static methods

Source: https://codewiki.com/javascript/object-methods/

> - **what**: `Object` static methods select own properties, transform key-value pairs, control property descriptors, and restrict object structure.
> - **trap**: Different methods select different keys, while both `Object.assign()` and `Object.freeze()` operate only one level deep.
> - **fix**: Decide among own or inherited, enumerable or non-enumerable, and string or Symbol before choosing a method; then test nested object identity.

## What it is and why it exists

`Object` static methods are object operations attached to the `Object` constructor, such as
`Object.keys(value)`, `Object.assign(target, source)`, and `Object.freeze(value)`. They aren't
instance methods that every object can safely call; the first argument names the object to inspect or change.

JavaScript objects store data under property keys. A key is either a string or a Symbol, and a property may
belong to the object itself or come from its prototype chain, with metadata
such as whether it is enumerable. “Get every field” is therefore incomplete until the caller defines which
property categories count.

These methods solve four recurring problems: projecting an object into a key-value list, constructing an
object from key-value pairs, reading or defining exact property behavior, and restricting whether an object
can gain, lose, or rewrite properties. You meet them in configuration merging, API projections, library
boundaries, and diagnostic tools.

The APIs don't share one universal meaning of “copy an object.” Copying plain data, retaining accessors,
retaining a prototype, and duplicating an entire object graph are different contracts. Pick the wrong method
and a new-looking object can lose metadata, invoke a getter, or continue sharing nested objects with its source.

Property removal still uses the `delete` operator, prototype-chain membership uses `in`, and a uniform
reflection interface comes from `Reflect`. These features sit beside `Object` methods but don't replace one another.

## How it works

### Three selection axes

Enumeration APIs mainly filter properties along three axes: whether a property is own or inherited, whether
it is enumerable, and whether its key is a string or Symbol. Write down those conditions before guessing from
a method name.

| Operation | Own string keys | Own Symbol keys | Inherited keys | Non-enumerable keys |
| --- | --- | --- | --- | --- |
| `Object.keys()` | Yes | No | No | No |
| `Object.values()` | Corresponding values | No | No | No |
| `Object.entries()` | Corresponding pairs | No | No | No |
| `Object.getOwnPropertyNames()` | Yes | No | No | Yes |
| `Object.getOwnPropertySymbols()` | No | Yes | No | Yes |
| `Reflect.ownKeys()` | Yes | Yes | No | Yes |
| `for...in` | Yes | No | Yes | No |

“Non-enumerable keys” in the table means that the operation can include that category. `Object.values()` and
`Object.entries()` select the same keys as `Object.keys()` and only change the result shape. Even an enumerable
Symbol property is omitted by all three methods.

`Object.hasOwn(object, key)` answers whether one key is an own property, regardless of enumerability, and it
accepts Symbols. It is safer than `object.hasOwnProperty(key)` because the target may override that method or
have no `Object.prototype` at all.

### Property descriptors control behavior

Every own property has a property descriptor. A data descriptor uses
`value` and `writable`; an accessor descriptor uses `get` and `set`. Both forms can use `enumerable` and
`configurable`, but one descriptor cannot specify data and accessor fields together.

Properties created by an object literal or ordinary assignment are normally writable, enumerable, and
configurable. Omitted fields in `Object.defineProperty()` have very different defaults: unspecified Boolean
flags are `false`. Writing only `{ value: 1 }` creates a non-writable, non-enumerable, non-configurable property.

`Object.getOwnPropertyDescriptor()` returns a descriptor snapshot; changing the returned object does not alter
the property. `Object.getOwnPropertyDescriptors()` returns descriptors for every own string and Symbol key. It
can be combined with `Object.create()` to build a new outer object with the same own-property behavior and prototype.

### Copies and transformations stay shallow

`Object.assign(target, ...sources)` reads each source's enumerable own string and Symbol keys from left to
right, then writes their values to the target. A later source overwrites an earlier source's same-named key.
The target is changed in place and is also the return value.

This operation produces a shallow copy. When a property value is an object,
only the reference to that same object is copied; nested objects aren't merged recursively. If a source
property is a getter, copying executes it and puts the resulting value in the target instead of preserving its descriptor.

`Object.entries()` projects an object into an array of string-keyed pairs for filtering or mapping with array
methods. `Object.fromEntries(iterable)` constructs an ordinary object from pairs. The combination is useful for
data transformations, but it isn't a lossless round trip: `Object.entries()` omits Symbols, non-enumerable
properties, descriptors, and the prototype.

### Integrity levels constrain one layer

`Object.preventExtensions()`, `Object.seal()`, and `Object.freeze()` all modify and return the supplied object.
They progressively restrict the object's own structure but don't recurse into objects referenced by property values.

| Object after operation | Add property | Delete property | Reconfigure property | Write an originally writable data property |
| --- | --- | --- | --- | --- |
| `preventExtensions()` | No | Yes | Yes | Yes |
| `seal()` | No | No | No | Yes |
| `freeze()` | No | No | No | No |

`seal()` makes every own property non-configurable; `freeze()` also makes own data properties non-writable.
Accessor properties don't have a `writable` flag, so getters still run on a frozen object and an existing setter
may still change other state. “Frozen” is therefore an integrity state for one object, not immutability for an
arbitrary object graph.

When direct assignment or `delete` conflicts with an integrity restriction, strict mode throws `TypeError` while
a non-strict script may fail silently. `Reflect.set()` and `Reflect.deleteProperty()` report these ordinary
failures as Booleans, making their result explicit in examples.

## Examples

These four examples successively verify property selection, key-value transformation, descriptor copying, and
integrity levels. Every output shown here came from local Node 24.14.0.

### Selecting the exact property set

This object has an inherited property, a non-enumerable property, and a Symbol property. Putting every category
in one sample makes the boundaries of the common APIs visible.

<!-- quick -->

```javascript
// file: inspect-profile.js
const token = Symbol('token');
const baseProfile = { inheritedRole: 'reader' };
const profile = Object.create(baseProfile);

Object.defineProperties(profile, {
  name: { value: 'Ada', enumerable: true },
  internalId: { value: 17, enumerable: false },
  [token]: { value: 'secret', enumerable: true },
});

console.log(JSON.stringify(Object.keys(profile)));
console.log(JSON.stringify(Object.values(profile)));
console.log(JSON.stringify(Object.entries(profile)));
console.log(Reflect.ownKeys(profile).map(String).join(','));
console.log(Object.hasOwn(profile, 'inheritedRole'));
console.log('inheritedRole' in profile);
```

```text
["name"]
["Ada"]
[["name","Ada"]]
name,internalId,Symbol(token)
false
true
```


<!-- /quick -->

`Object.keys()` returns only `name`, not because the other properties don't exist but because they don't satisfy
all three conditions: own, enumerable, and string-keyed. `in` follows the prototype chain, which is why the last
two lines give different answers for the same key.

At a boundary with an allowlist, don't begin with “every key” and delete a list of known dangerous fields.
Read the permitted names directly and use `Object.hasOwn()` to distinguish missing fields from inherited ones;
that contract remains stable as the input grows.

### Transforming and merging plain data

The first transformation removes an internal field. The configuration merge then demonstrates two independent
facts: later sources overwrite earlier keys, and the nested `limits` object is replaced as a unit rather than
merged recursively.

```javascript
// file: transform-config.js
const flags = {
  checkout: true,
  internalNote: 'remove',
  retries: 0,
};

const publicFlags = Object.fromEntries(
  Object.entries(flags).filter(([key]) => !key.startsWith('internal')),
);
console.log(JSON.stringify(publicFlags));

const defaults = { theme: 'light', limits: { requests: 100 } };
const input = { theme: 'dark', limits: { burst: 10 } };
const merged = Object.assign({}, defaults, input);

console.log(JSON.stringify(merged));
console.log(merged === defaults, merged.limits === input.limits);

const nestedMerge = {
  ...defaults,
  ...input,
  limits: { ...defaults.limits, ...input.limits },
};
console.log(JSON.stringify(nestedMerge));
```

```text
{"checkout":true,"retries":0}
{"theme":"dark","limits":{"burst":10}}
false true
{"theme":"dark","limits":{"requests":100,"burst":10}}
```

Using an empty object as the `Object.assign()` target avoids changing `defaults`, but it doesn't make nested
values independent. The `true` on the third output line proves that `merged.limits` and `input.limits` still
refer to the same object.

Explicitly spreading `limits` works for configuration with a known shape. A generic “deep merge” needs separate
policies for arrays, cycles, accessors, dangerous keys, and different object types; those policies don't follow
naturally from `Object.assign()`.

### Defining and retaining property descriptors

This example first exposes the defaults used by `defineProperty()`, then compares value copying with descriptor
copying. A source getter's read count makes the side effect visible.

```javascript
// file: copy-descriptors.js
'use strict';

const account = {};
Object.defineProperty(account, 'id', {
  value: 'A-17',
  enumerable: true,
});

const idDescriptor = Object.getOwnPropertyDescriptor(account, 'id');
console.log(JSON.stringify({
  writable: idDescriptor.writable,
  enumerable: idDescriptor.enumerable,
  configurable: idDescriptor.configurable,
}));

let reads = 0;
Object.defineProperty(account, 'balance', {
  get() {
    reads += 1;
    return 42;
  },
  enumerable: true,
  configurable: true,
});

const assigned = Object.assign({}, account);
const exact = Object.create(
  Object.getPrototypeOf(account),
  Object.getOwnPropertyDescriptors(account),
);

console.log(assigned.balance, reads);
console.log(typeof Object.getOwnPropertyDescriptor(assigned, 'balance').get);
console.log(typeof Object.getOwnPropertyDescriptor(exact, 'balance').get);
```

```text
{"writable":false,"enumerable":true,"configurable":false}
42 1
undefined
function
```

`Object.assign()` runs the `balance` getter once to obtain its value and creates an ordinary data property on
the target. `Object.getOwnPropertyDescriptors()` reads the accessor function itself, so creating `exact` does
not run the getter again.

Here `exact` only means that the outer prototype and own property descriptors are retained. Property values
still share references, and built-ins or class instances with internal slots or private fields cannot be turned
into universal exact clones with this combination.

### Comparing the three integrity levels

Using `Reflect` exposes whether each operation succeeds without depending on strict-mode error text. The last
two lines also verify that freezing affects only the outer object.

```javascript
// file: integrity-levels.js
const extensible = { count: 1 };
Object.preventExtensions(extensible);
console.log(
  Reflect.set(extensible, 'count', 2),
  Reflect.set(extensible, 'extra', true),
  Reflect.deleteProperty(extensible, 'count'),
);

const sealed = Object.seal({ count: 1 });
console.log(
  Reflect.set(sealed, 'count', 2),
  Reflect.set(sealed, 'extra', true),
  Reflect.deleteProperty(sealed, 'count'),
);

const preferences = { theme: 'light' };
const frozen = Object.freeze({ count: 1, preferences });
console.log(
  Reflect.set(frozen, 'count', 2),
  Reflect.deleteProperty(frozen, 'count'),
  Object.isFrozen(frozen),
);

preferences.theme = 'dark';
console.log(frozen.preferences.theme);
console.log(Object.isFrozen(frozen.preferences));
```

```text
true false true
true false false
false false true
dark
false
```

A non-extensible object still permits changes to and deletion of existing properties. A sealed object still
permits writes to originally writable values; a frozen object refuses those writes too. None of the three
operations follows the `preferences` reference, so the nested object remains mutable.

`Object.isFrozen()` answers only whether its argument is frozen. If a contract claims that an entire input graph
is immutable, tests must continue through every relevant nested object or the design must establish explicit
ownership of immutable data.

## Pitfalls

### Treating `Object.keys()` as every property

> **Pitfall:** `Object.keys()` omits non-enumerable properties, Symbol properties, and inherited properties. A debugger showing a field doesn't mean the field appears in `Object.keys()`, object spread, or JSON output.

**Fix:** choose a key set from the requirement. Use `Reflect.ownKeys()` for every own key, `Object.hasOwn()` for
one own-key test, and an explicit, filtered `for...in` only when prototype-chain enumeration is intentional.

### Letting `Object.assign()` mutate shared defaults

> **Pitfall:** `Object.assign(defaults, input)` uses `defaults` as the target and changes it in place. Module-level defaults then retain data from a previous request, and later sources may also invoke a setter on the target.

**Fix:** merge plain data into a fresh target, as in `Object.assign({}, defaults, input)`, and handle nested fields
separately. For untrusted input, construct an allowlisted result instead of writing every enumerable key into an
ordinary object.

### Mistaking a shallow copy for an independent copy

> **Pitfall:** `Object.assign()`, object spread, and descriptor copying create only a new outer object. Mutating a shared nested array or object still changes what the source observes.

**Fix:** state which levels require independent identity and copy only those levels. When a general object graph
must be copied, use a mechanism with a contract matching the data types, then test shared references, cycles,
and unsupported values instead of applying a JSON round trip.

### Forgetting descriptor defaults

> **Pitfall:** `Object.defineProperty(target, 'port', { value: 8080 })` creates a property that is non-writable, non-enumerable, and non-configurable by default. Generated code often supplies only `value` and then misdiagnoses a later failed assignment as a freezing problem.

**Fix:** spell out every flag the business behavior depends on and assert the result with
`Object.getOwnPropertyDescriptor()`. Making a property non-configurable is a strong one-way decision; confirm
that later code won't need to redefine or delete it.

### Assuming freeze recurses or disables accessors

> **Pitfall:** `Object.freeze()` doesn't freeze nested objects or turn a getter into a fixed value. On a frozen accessor property, the getter and an existing setter can still run and read or change state elsewhere.

**Fix:** describe freezing as an outer integrity constraint and test nested identity and accessor side effects.
Before implementing recursive freezing, define how to handle cycles, Symbol keys, accessors, proxies, typed
arrays, and instances with private state.

<!-- deep -->

## Property order and round-trip boundaries

Ordinary own keys follow a defined order. Array-index string keys come first in ascending numeric order, other
string keys follow creation order, and Symbol keys follow in creation order at the end. `Object.keys()`,
`Object.values()`, `Object.entries()`, and `Reflect.ownKeys()` preserve this order within their selected categories.

“Array-index string” is narrower than “looks numeric.” `'2'` and `'10'` participate in index ordering, while
`'01'`, `'-1'`, and ordinary names stay in string creation order. Tests for order-sensitive code should include
values that expose these boundaries.

`Object.fromEntries()` accepts any iterable of key-value pairs and converts non-Symbol keys to strings. Repeated
keys overwrite in iteration order, leaving the final value; a Symbol key can become a property key directly.

This explains why `Object.fromEntries(Object.entries(object))` is not a general inverse operation.
`Object.entries()` has already removed Symbols, non-enumerable properties, inherited properties, descriptors,
and prototype information, so the second step cannot recreate them. “Data projection” is a better name for this
combination than “object clone.”

`Object.keys()` selects keys without reading their property values, so it doesn't itself execute getters.
`Object.values()`, `Object.entries()`, object spread, and `Object.assign()` must read selected values, which runs
getters. A Proxy can also intercept the relevant internal operations, so its result needs separate verification
against the proxy contract.

## Observable behavior of copy operations

The differences among copying operations go beyond syntax. Source reads, target writes, key selection, and the
property-creation mechanism can all be observed by a program.

| Operation | Source keys | Source getter | Target setter | Preserves descriptors |
| --- | --- | --- | --- | --- |
| `Object.assign(target, source)` | Enumerable own strings and Symbols | Runs | May run | No |
| `{ ...source }` | Enumerable own strings and Symbols | Runs | Doesn't run an existing target setter | No |
| `Object.fromEntries(entries)` | Keys supplied by iterator | Depends on iterator | No | No |
| `Object.create(proto, descriptors)` | Keys supplied by descriptors | Doesn't read source property values | No | Yes |

Object spread creates own data properties on a new object literal rather than writing by ordinary assignment to
an existing target as `Object.assign()` does. Both still read source properties, so spread does not avoid side
effects from source getters.

Descriptor copying preserves getters, setters, and flags instead of saving a getter's current result as a data
property. It still reuses object values and accessor functions from those descriptors, and it doesn't copy
closure state, private fields, or the internal slots of built-ins.

`Object.create(Object.getPrototypeOf(source), Object.getOwnPropertyDescriptors(source))` therefore implements
one narrow contract: retain the outer prototype and every own property descriptor. Calling it
`cloneWithDescriptors` can be accurate; calling it `deepClone` without qualification misleads callers.

`Object.assign()` ignores a source whose value is `null` or `undefined`, but either value as the target causes a
`TypeError`. Other primitives are first converted to wrapper objects; except for string characters, they usually
have no enumerable own properties to copy. A boundary API that expects plain records should validate that
requirement rather than depend on coercion details.

## Integrity boundaries and recursive freezing

Integrity methods operate on the supplied object itself and attempt to adjust its extensibility and own property
descriptors. They aren't copy operations, and variables continue to point to the same object. Consequently,
`Object.freeze(value) === value` is always true.

Freezing an array prevents element writes, element deletion, and changes to `length` because those behaviors are
represented by the array's own properties. Objects referenced by its elements remain mutable. The same shallow
boundary applies to ordinary objects: freezing a container doesn't freeze its members.

A reliable recursive freezer must at least record visited objects to handle cycles and use `Reflect.ownKeys()`
to include Symbol keys. It must also inspect descriptors to avoid executing a getter merely to traverse an
accessor property. Proxies can intercept inspection and freezing operations, while different built-ins may add
their own restrictions.

That is why the short `Object.values(value).forEach(deepFreeze)` recipe isn't a general utility. It skips
non-enumerable and Symbol values, fails on cycles, and executes user code when it reads an accessor. With a fixed
data model, implement and test a recursion policy for that model; otherwise narrow the meaning of “deeply immutable” first.

Freezing is not a confidentiality or authorization boundary either. Read access remains, and state in closures,
private fields, a `WeakMap`, or an external service is outside the object's own descriptors. Security review must
trace data exposure and permission checks instead of treating `isFrozen()` as a trust marker.

## Null-prototype dictionaries and prototype choice

`Object.create(null)` creates an object without `Object.prototype`. It inherits no `toString`, `constructor`, or
`hasOwnProperty`, so code that assumes those instance methods exist on every object fails.

`Object.keys()`, `Object.entries()`, `Reflect.ownKeys()`, and `Object.hasOwn()` all work directly with
null-prototype objects. When string keys come from external input, such a dictionary also avoids ordinary
inherited-name collisions with `Object.prototype`.

A null-prototype object still isn't a complete substitute for `Map`. When you need keys of any type, direct
`size`, an explicit insertion and deletion interface, or no string-key coercion, `Map` often fits the contract
better. Base the choice on key types and interface rather than a blanket claim that one container is faster.

`Object.create(prototype)` sets an intended prototype at creation. `Object.setPrototypeOf()` changes an existing
object's property-lookup relationships and can violate callers' assumptions about its type. Business code should
usually create the object with its intended prototype or use a class to express the public interface instead of
swapping prototypes after the object is in circulation.

<!-- /deep -->

[Checkpoint: javascript/object-methods](https://codewiki.com/javascript/object-methods/#checkpoint)

## Further reading

- [MDN: `Object`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)
- [MDN: Enumerability and ownership of properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Enumerability_and_ownership_of_properties)
- [MDN: `Object.defineProperty()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)
- [MDN: `Object.freeze()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)
- [ECMAScript language specification: Object Objects](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object-objects)
