# JavaScript Property Behavior Matrix

> Source: <https://gist.github.com/maronarius/059fcaa1918ccaab7075962536787f67>
> Published: 2026-08-01 21:09:13+00:00

A comprehensive reference for how JavaScript property types interact with language operators, loops, spread, and Object static methods.

Document Note:Created by AI (Gemini) in collaboration with the user.

**Enum vs Non-Enum:** Refers to property enumerability (`enumerable: true`

vs`enumerable: false`

),*not*TypeScript`enum`

types.**String vs Symbol:** Refers to the property key type (String keys like`"a"`

vs Symbol keys like`Symbol("a")`

).**Own vs Inherited:** Properties defined directly on the object instance vs properties inherited through the prototype chain.

— Property value is evaluated. If it's an accessor, its`✓ Accessed`

**Getter** is executed.— Existing own property value is updated (invokes an own`✓ Overwritten`

**Setter** if defined).— Assignment creates a new`⬖ Masked`

**own data property** on`obj`

, masking the prototype (or invokes an inherited**Setter** if defined).`✓ Deleted`

— Own property is removed from the object.[1](https://gist.github.com/starred.atom#user-content-fn-1-bacf6fede3ac5ab18924c7e40bf71080)— Key exists and is detected (`✓ Found`

`true`

returned by existence check).— Property is included in the output array or loop iteration. For`✓ Included`

`Object.values()`

and`Object.entries()`

, reading the value invokes**Getters** if present.— Value is evaluated (executing`✓ Copied`

**Getters** if present) and assigned as a data property.— Raw descriptor object`✓ Described`

`{ get, set, value, ... }`

is read directly without invoking accessors.— Property is skipped/bypassed (or returns`✗ Ignored`

`false`

on existence checks, same as for missing properties).

| Situation / Operation | Own String (Enum) | Own String (Non-Enum) | Own Symbol (Enum) | Own Symbol (Non-Enum) | Inherited String (Enum) | Inherited (Non-Enum or Symbol) |
|---|---|---|---|---|---|---|
`val = obj.prop` / `val = obj[prop]` |
✓ Accessed |
✓ Accessed |
✓ Accessed |
✓ Accessed |
✓ Accessed |
✓ Accessed |
`obj.prop = val` / `obj[prop] = val` |
✓ Overwritten |
✓ Overwritten |
✓ Overwritten |
✓ Overwritten |
⬖ Masked |
⬖ Masked |
`delete obj.prop` / `delete obj[prop]` |
✓ Deleted |
✓ Deleted |
✓ Deleted |
✓ Deleted |
✗ Ignored |
✗ Ignored |
`prop in obj` |
✓ Found |
✓ Found |
✓ Found |
✓ Found |
✓ Found |
✓ Found |
`Object.hasOwn(obj, prop)` / `obj.hasOwnProperty(prop)` |
✓ Found |
✓ Found |
✓ Found |
✓ Found |
✗ Ignored |
✗ Ignored |
`for (prop in obj)` |
✓ Included |
✗ Ignored |
✗ Ignored |
✗ Ignored |
✓ Included |
✗ Ignored |
`Object.keys()` / `Object.values()` / `Object.entries()` |
✓ Included |
✗ Ignored |
✗ Ignored |
✗ Ignored |
✗ Ignored |
✗ Ignored |
`{ ...obj }` |
✓ Copied |
✗ Ignored |
✓ Copied |
✗ Ignored |
✗ Ignored |
✗ Ignored |
`Object.assign(target, src)` |
✓ Copied
|
✗ Ignored |
✓ Copied
|
✗ Ignored |
✗ Ignored |
✗ Ignored |
`structuredClone(obj)` |
✓ Copied
|
✗ Ignored |
✗ Ignored |
✗ Ignored |
✗ Ignored |
✗ Ignored |
`JSON.stringify(obj)` |
✓ Copied
|
✗ Ignored |
✗ Ignored |
✗ Ignored |
✗ Ignored |
✗ Ignored |
`Object.getOwnPropertyNames(obj)` |
✓ Included |
✓ Included |
✗ Ignored |
✗ Ignored |
✗ Ignored |
✗ Ignored |
`Object.getOwnPropertySymbols(obj)` |
✗ Ignored |
✗ Ignored |
✓ Included |
✓ Included |
✗ Ignored |
✗ Ignored |
`Reflect.ownKeys(obj)` |
✓ Included |
✓ Included |
✓ Included |
✓ Included |
✗ Ignored |
✗ Ignored |
`Object.getOwnPropertyDescriptor` / `Object.getOwnPropertyDescriptors` |
✓ Described |
✓ Described |
✓ Described |
✓ Described |
✗ Ignored |
✗ Ignored |

## Footnotes

-
`delete`

Operator Return Values & Mechanics:`delete`

only affects**own properties**, leaving inherited prototype properties untouched (and returning`true`

). It returns`true`

if an own property was successfully deleted, or if the property never existed in the first place. It returns`false`

in non-strict mode (or throws a`TypeError`

in strict mode) when attempting to delete an**own non-configurable property**.[↩](https://gist.github.com/starred.atom#user-content-fnref-1-bacf6fede3ac5ab18924c7e40bf71080) -
While both copy values (invoking Getters on`Object.assign`

vs. Object Spread (`{ ...obj }`

):`src`

),`Object.assign(target, src)`

assigns values to`target`

via`target[k] = val`

. Thus, it triggers**Setters** if`target`

(or its prototype chain) already defines a matching setter. Spread (`{ ...src }`

) always defines new**own data properties** on the object literal being constructed, completely bypassing any existing target setters.[↩](https://gist.github.com/starred.atom#user-content-fnref-2-bacf6fede3ac5ab18924c7e40bf71080)[↩](https://gist.github.com/starred.atom#user-content-fnref-2-2-bacf6fede3ac5ab18924c7e40bf71080)2 -
Executes Getters on own enumerable string properties and recursively deep-clones values.`structuredClone(obj)`

Constraints:**Throws a** if any property value is a`DataCloneError`

**Function** or**Method**. Symbols, non-enumerable properties, and prototype chains are completely stripped.[↩](https://gist.github.com/starred.atom#user-content-fnref-3-bacf6fede3ac5ab18924c7e40bf71080) -
Executes Getters on own enumerable string properties. Automatically`JSON.stringify(obj)`

Constraints:**omits** properties whose value evaluates to a**Function**,** Symbol**, or`undefined`

(when inside objects), or converts them to`null`

(when inside arrays).[↩](https://gist.github.com/starred.atom#user-content-fnref-4-bacf6fede3ac5ab18924c7e40bf71080)
