JavaScript: Objects

Learn to create, access, modify, and transform objects using every built-in method, destructuring, spread, getters, setters, and the this keyword.

Object Creation

Objects are most commonly created with the object literal syntax {}. The new Object() constructor exists but is verbose and rarely used. Object.create() gives fine control over the prototype.

Object Creation Methods

Three ways to create objects. The literal syntax is the standard for everyday use.

  • Literal: const obj = { key: value }, preferred
  • Constructor: const obj = new Object(), avoid, verbose
  • Object.create(proto): creates an object with a specific prototype
  • Factory function: a regular function that returns a new object literal

Object Creation

Factory functions are a simple way to create many similar objects without classes.

Object Properties

An object is a collection of key-value pairs. Keys are strings (or Symbols) and values can be any type including other objects or functions. Properties defined at creation time sit directly on the object.

Key-Value Pairs

Every property has a string key and a value of any type.

  • Keys are strings by default, quotes optional when the key is a valid identifier
  • Values can be primitives, arrays, objects, or functions
  • A property whose value is a function is called a method
  • Keys with spaces or special characters require quotes: "first-name": "Alice"

Object Properties

Keys with hyphens or spaces must be quoted and accessed with bracket notation.

Accessing Properties

There are two ways to read a property: dot notation for known keys and bracket notation for dynamic keys or keys that are not valid identifiers. Accessing a property that does not exist returns undefined.

Dot vs Bracket Notation

Use dot notation by default. Use bracket notation when the key is dynamic or contains special characters.

  • Dot: obj.name, clean, works when key is a valid identifier
  • Bracket: obj["name"] or obj[variable], required for dynamic keys
  • Missing property returns undefined, not an error
  • Optional chaining obj?.prop prevents TypeError on null/undefined objects

Dot vs Bracket Notation

user[key] where key is a variable is the most common reason to reach for bracket notation.

Adding and Modifying Properties

Properties can be added or updated at any time simply by assigning to a key. If the key exists, the value is overwritten. If it does not exist, a new property is created.

Adding and Updating

Assignment creates or overwrites. const on the object prevents rebinding, not mutation.

  • obj.newKey = value: adds if absent, updates if present
  • Computed keys in literals: { [varName]: value }
  • const prevents reassigning the variable, not changing its properties
  • Use Object.freeze() if you want to block mutations entirely

Adding and Modifying Properties

Computed keys [field] let you use a variable as a property name at creation time.

Deleting Properties

The delete operator removes a property from an object. After deletion, accessing the key returns undefined and the key no longer appears in Object.keys().

delete Operator

Removes a property entirely. Setting to undefined leaves the key present.

  • delete obj.key: removes the property and its key
  • Returns true on success (even if the property never existed)
  • Cannot delete non-configurable properties
  • Prefer object spread for immutable removal: const { key, ...rest } = obj

Deleting Properties

Spread destructuring is the immutable way to remove a property without modifying the original.

Checking Property Existence

Two tools check whether a property exists: the in operator and Object.hasOwn(). They differ in whether they search the prototype chain.

in vs hasOwn

Use hasOwn for own properties only. Use in when inherited properties should also match.

  • "key" in obj: true for own and inherited enumerable properties
  • Object.hasOwn(obj, "key"): true for own properties only (ES2022)
  • obj.hasOwnProperty("key"): same as hasOwn but can be overridden
  • Never check with obj.key !== undefined: the property may exist but be set to undefined

in vs Object.hasOwn

toString is inherited so 'in' finds it but hasOwn does not. Always use hasOwn for own-property checks.

Object.keys(), Object.values(), Object.entries()

These three static methods return arrays of the object's own enumerable properties, making objects iterable with standard array methods.

keys / values / entries

Convert object data into arrays so you can use map, filter, and forEach.

  • Object.keys(obj): array of property names
  • Object.values(obj): array of property values
  • Object.entries(obj): array of [key, value] pairs
  • All three return only own enumerable properties
  • Pair entries with Object.fromEntries() to convert back to an object

keys, values, entries

entries + fromEntries is the pattern for transforming object values immutably.

Object.assign()

Object.assign(target, ...sources) copies own enumerable properties from one or more source objects into the target. It is a shallow copy, nested objects are still shared references.

Object.assign()

Shallow merge into target. For immutable merging, prefer spread: { ...a, ...b }.

  • Mutates the target object: pass {} as first arg to avoid side effects
  • Later sources overwrite earlier ones for the same key
  • Shallow: nested objects are copied by reference, not cloned
  • Spread { ...a, ...b } is equivalent and does not mutate anything

Object.assign()

Always pass {} as the first argument to avoid mutating a source. Watch out for shared nested references.

Object.freeze() and Object.seal()

freeze() makes an object fully immutable. seal() allows updating existing properties but blocks adding or deleting. Both are shallow.

freeze vs seal

freeze blocks everything. seal blocks add/delete but allows updates.

  • Object.freeze(obj): no add, no delete, no update
  • Object.seal(obj): no add, no delete, but existing values can change
  • Both are shallow: nested objects are not frozen/sealed automatically
  • Mutations silently fail in non-strict mode; throw in strict mode
  • Object.isFrozen(obj) / Object.isSealed(obj) to check status

freeze and seal

freeze prevents all changes. seal allows updating existing values but nothing else.

Object.create()

Object.create(proto) creates a new object whose internal prototype is set to proto. This is the low-level mechanism behind prototype-based inheritance.

Object.create()

Creates an object that inherits from a specific prototype, useful for prototypal patterns.

  • Object.create(proto): new object with proto as its prototype
  • Object.create(null): pure object with no prototype at all
  • Properties defined on proto are shared across all created objects
  • Classes and constructor functions use this mechanism internally

Object.create()

Object.create(null) creates a true dictionary with no prototype baggage, useful for lookup tables.

Nested Objects

Object properties can themselves be objects, creating nested structures. Access nested values by chaining dot or bracket notation. Use optional chaining ?. to safely navigate paths that may not exist.

Nested Objects

Chain dot notation to reach deep properties. Use ?. to avoid TypeError on missing paths.

  • Access: obj.address.city
  • Safe access: obj?.address?.city returns undefined instead of throwing
  • Modifying a nested value mutates the original: clone deeply if needed
  • Deep clone: JSON.parse(JSON.stringify(obj)) for plain data (no functions/Dates)

Nested Objects

JSON.parse(JSON.stringify(obj)) deep clones plain objects but strips functions and Date values.

Object Destructuring

Object destructuring extracts properties into variables in one concise statement. Unlike array destructuring, it matches by key name, not position.

Object Destructuring

Unpack properties into variables by name. Rename, set defaults, and use rest all in one line.

  • Syntax: const { name, age } = user;
  • Rename: const { name: fullName } = user;
  • Default value: const { role = "viewer" } = user;
  • Nested: const { address: { city } } = user;
  • Rest: const { name, ...rest } = user;

Object Destructuring

Rename with a colon: { name: fullName } reads as 'take name, call it fullName'.

Spread Operator with Objects

The spread operator ... copies own enumerable properties into a new object. It is the immutable alternative to Object.assign() and produces no side effects.

Object Spread

Copy and merge objects immutably. Later properties overwrite earlier ones.

  • Clone: const copy = { ...obj };
  • Merge: const merged = { ...a, ...b };
  • Override a specific key: { ...user, role: "admin" }
  • Shallow: nested objects are still shared references

Object Spread

{ ...user, role: 'admin' } is the clean pattern for updating one field without mutation.

this Keyword in Objects

Inside an object method, this refers to the object the method is called on. Arrow functions do not have their own this: they inherit it from the surrounding scope, which makes them unsuitable as object methods.

this in Object Methods

this is the object to the left of the dot at call time. Arrow functions inherit this from outside.

  • Regular method: this refers to the calling object
  • Arrow function as method: this is from the outer scope, not the object
  • this is determined at call time, not definition time
  • Destructured method loses this: bind or use an arrow wrapper if needed

this in Object Methods

Never use arrow functions as object methods when you need to access the object via this.

Getters and Setters

Getters and setters define computed or validated properties using get and set keywords. They look like plain properties to the caller but run a function behind the scenes.

get / set

Properties that run logic on read or write, without the caller knowing.

  • get propName(): called when the property is read
  • set propName(value): called when the property is assigned
  • Getters have no parameters; setters have exactly one
  • Use for computed values, validation, or lazy initialisation

Getters and Setters

fullName looks like a plain property but runs a function on every read and write.

Object Shorthand Notation

ES6 introduced shorthand syntax for two common patterns: property shorthand (when the variable name matches the key) and method shorthand (omitting function keyword from methods).

Shorthand Notation

Write less when the variable name already matches the property name.

  • Property shorthand: { name } instead of { name: name }
  • Method shorthand: greet() { } instead of greet: function() { }
  • Computed shorthand: { [key]: value }, dynamic key at creation
  • Commonly used in function return values and React props

Object Shorthand

Returning this from methods enables fluent chaining: calc.add(5).add(3).reset().

Knowledge Check

1. Which notation must you use to access a property whose name is stored in a variable?

2. What does Object.freeze() do?

3. What does Object.entries() return?

4. What does the "in" operator check?

5. What does Object.assign() do?

6. What does "this" refer to inside an object method when called normally?

7. What is shorthand property notation?

8. What does Object.create(proto) do?