Skip to main content

Command Palette

Search for a command to run...

JavaScript Objects: A Comprehensive Guide with ES6 Syntax

Updated
14 min readView as Markdown
JavaScript Objects: A Comprehensive Guide with ES6 Syntax
G

Hello! I'm a dedicated software developer with a passion for coding and a belief in technology's impact on our world. My programming journey started a few years ago and has reshaped my career and mindset. I love tackling complex problems and creating efficient code. My skills cover various languages and technologies like JavaScript, Angular, ReactJS, NodeJs, and Go Lang. I stay updated on industry trends and enjoy learning new tools. Outside of coding, I cherish small routines that enhance my workday, like sipping tea, which fuels my creativity and concentration. Whether debugging or brainstorming, it helps me focus. When I'm not coding, I engage with fellow developers. I value teamwork and enjoy mentoring newcomers and sharing my knowledge to help them grow. Additionally, I explore the blend of technology and creativity through projects that incorporate art and data visualization. This keeps my perspective fresh and my passion alive. I'm always seeking new challenges, from open-source contributions to hackathons and exploring AI. Software development is more than a job for me—it's a passion that drives continuous learning and innovation.

Last updated: September 2026 — fixed two broken code samples and two wrong outputs; added optional chaining, the shallow-copy trap, structuredClone, Object.freeze, accessors, descriptors, fromEntries and groupBy.

Welcome to a comprehensive journey through the world of JavaScript objects. In this guide we'll explore objects in depth, using both Vanilla JS and modern ES6+ syntax — how to create them, query them, copy them, and the places where they behave differently from what you expect.

Now, let's dive into the details of JavaScript objects.

1. Introduction to Objects

JavaScript objects are versatile data structures used for organizing and storing data efficiently. Unlike primitive data types, such as numbers and strings, objects can hold various data types, including other objects, functions, and arrays. They are fundamental to JavaScript and are key components of web development.

Example: Creating a Simple Object (ES6 Syntax)

const person = {
  firstName: 'John',
  lastName: 'Doe',
  age: 30,
};

In this example, person is an object with three properties: firstName, lastName, and age, each representing distinct pieces of information.

The one idea behind most object bugs

Before anything else: objects are held by reference, not by value. Two variables can point at the same object, and changing it through one changes it through the other.

const a = { count: 1 };
const b = a;

b.count = 99;
console.log(a.count);   // 99 — a and b are the same object

const c = { count: 1 };
console.log(a === c);   // false — same contents, different objects

=== on objects asks "is this the same object?", never "do these look alike?". Keep that in mind through the rest of this article — copying and equality both come back to it.

2. Creating Objects

JavaScript offers multiple methods for creating objects, allowing developers to choose the most suitable approach for their needs.

a. Object Literal Notation

The simplest method is using object literal notation:

const person = {
  firstName: 'John',
  lastName: 'Doe',
  age: 30,
};

b. Constructor Functions

Constructor functions enable you to create objects with shared methods:

function Person(firstName, lastName, age) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.age = age;
}

const person = new Person('John', 'Doe', 30);

c. ES6 Class Syntax

ES6 introduced class syntax for object creation, providing a structured approach:

class Person {
  constructor(firstName, lastName, age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }
}

const person = new Person('John', 'Doe', 30);

d. Object.create() Method

The Object.create() method creates an object with a specified prototype:

const personPrototype = {
  greet() {
    console.log(`Hello, my name is ${this.firstName}`);
  },
};

const person = Object.create(personPrototype);
person.firstName = 'John';
person.lastName = 'Doe';
person.age = 30;

person.greet();   // "Hello, my name is John"

greet is not on person — it is on its prototype, found by the lookup chain. That distinction matters when we get to enumeration in section 6.

3. Querying and Setting Properties

Accessing and modifying object properties can be accomplished through dot notation or square bracket notation.

Example: Querying and Setting Properties

const person = { firstName: 'John', lastName: 'Doe', age: 30 };

// Querying properties
console.log(person.firstName);     // 'John'   dot notation
console.log(person['firstName']);  // 'John'   bracket notation

// Setting properties
person.age = 31;
console.log(person.age);           // 31

// A property that does not exist
console.log(person.email);         // undefined — no error

Use dot notation by default. Brackets are for when the key is in a variable, or is not a valid identifier:

const key = 'lastName';
console.log(person[key]);          // 'Doe'
console.log(person.key);           // undefined — looks for a property literally named "key"

const weird = { 'first name': 'John' };
console.log(weird['first name']);  // brackets are the only option here

Optional chaining

Reading a missing property gives undefined. Reading a property of undefined throws:

const user = { profile: { name: 'Ganesh' } };

console.log(user.profile.name);   // 'Ganesh'
console.log(user.address.city);   // TypeError: Cannot read properties of undefined

Before ES2020 the defensive version was noisy:

const city = user && user.address && user.address.city;

?. does the same thing in one character:

console.log(user.address?.city);       // undefined
console.log(user.profile?.['name']);   // 'Ganesh' — works with brackets
console.log(user.getName?.());         // undefined — and with method calls

The short-circuit covers the rest of the chain, so user.address?.city.zip does not throw either — once address is undefined, nothing after it evaluates.

Pair it with ?? for a fallback:

const city = user.address?.city ?? 'Unknown';

Use it where a value is genuinely optional. If a property should always be there, let the code throw — a crash at the real cause is easier to fix than an undefined spreading silently.

4. Deleting Properties

To remove a property from an object, use the delete operator:

Example: Deleting a Property

const person = { firstName: 'John', lastName: 'Doe', age: 30 };

delete person.lastName;
console.log(person);   // { firstName: 'John', age: 30 }

delete returns true even when the property never existed, so it is not a useful check. And it only removes own properties — a property inherited from a prototype is untouched.

5. Testing Properties

There are three ways to ask whether a property exists, and they answer slightly different questions.

const person = { firstName: 'John', age: 30 };

console.log('age' in person);                  // true  — own OR inherited
console.log(Object.hasOwn(person, 'age'));     // true  — own only (ES2022)
console.log(person.hasOwnProperty('age'));     // true  — own only (older form)

in also finds inherited properties, which is usually not what you want:

console.log('toString' in person);              // true  — inherited from Object.prototype
console.log(Object.hasOwn(person, 'toString')); // false

Prefer Object.hasOwn() over person.hasOwnProperty(). The old form is a method on the object, so it breaks on an object that does not inherit from Object.prototype:

const bare = Object.create(null);
bare.a = 1;

bare.hasOwnProperty('a');       // TypeError: bare.hasOwnProperty is not a function
Object.hasOwn(bare, 'a');       // true — works

Note that none of these distinguishes "missing" from "present but undefined". For that, use Object.hasOwn.

6. Enumerating Properties

JavaScript provides several ways to iterate over object properties.

a. Using for...in Loops

const person = { firstName: 'John', age: 30 };

for (const key in person) {
  console.log(`${key}: ${person[key]}`);
}

Careful here. for...in walks the prototype chain, so it lists inherited properties too. Look what happens with the Object.create() object from section 2d:

const personPrototype = { greet() { /* ... */ } };
const p = Object.create(personPrototype);
p.firstName = 'John';
p.lastName = 'Doe';
p.age = 30;

for (const key in p) console.log(key);
// firstName
// lastName
// age
// greet        <-- inherited, almost certainly not wanted

If you use for...in, guard it:

for (const key in p) {
  if (!Object.hasOwn(p, key)) continue;
  console.log(key);
}

Most of the time the methods below are simpler and safer.

b. Using the Object.keys() Method

Object.keys() returns own, enumerable keys only — no prototype chain, no surprises:

const keys = Object.keys(p);
console.log(keys);   // [ 'firstName', 'lastName', 'age' ]

for (const key of keys) {
  console.log(`${key}: ${p[key]}`);
}

c. Object.entries() with destructuring

The cleanest form for most loops, because you get key and value together:

for (const [key, value] of Object.entries(p)) {
  console.log(`${key}: ${value}`);
}

7. Extending Objects

Dynamically adding properties and methods to objects enhances their functionality.

Example: Extending an Object (ES6 Syntax)

class Person {
  constructor(firstName, lastName, age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  greet() {
    console.log(`Hello, my name is ${this.firstName}`);
  }
}

const person = new Person('John', 'Doe', 30);
person.greet();   // "Hello, my name is John"

Copying objects — and the trap in it

Spread and Object.assign() both copy the top level only. Nested objects are shared between the original and the copy — they are the same object at two addresses.

const original = { name: 'John', address: { city: 'Pune' } };
const copy = { ...original };

copy.name = 'Someone';
console.log(original.name);           // 'John'   — top level is independent

copy.address.city = 'Mumbai';
console.log(original.address.city);   // 'Mumbai' <-- the original changed too

That second one is the bug. It usually appears as "I changed the copy and the state updated" in a React component.

The old workaround was JSON.parse(JSON.stringify(obj)), which works right up until it doesn't — see section 8 for what it silently destroys.

structuredClone() is the built-in that does it properly:

const deep = structuredClone(original);
deep.address.city = 'Mumbai';
console.log(original.address.city);   // 'Pune' — genuinely independent

It handles Date, Map, Set, RegExp, typed arrays and circular references. It cannot clone functions, DOM nodes or symbols — it throws on those, which is better than silently dropping them.

Freezing an object

const stops you reassigning the variable. It does nothing about the contents:

const config = { retries: 3 };
config.retries = 5;
console.log(config.retries);   // 5 — const did not prevent this

Object.freeze stops the contents changing:

const frozen = Object.freeze({ retries: 3 });
frozen.retries = 5;
console.log(frozen.retries);   // 3 — the write is ignored (throws in strict mode)

Freeze is shallow too, so freezing deeply means recursing:

function deepFreeze(obj) {
  for (const value of Object.values(obj)) {
    if (value && typeof value === 'object') deepFreeze(value);
  }
  return Object.freeze(obj);
}

Two weaker relatives exist: Object.seal() (existing properties stay writable, none can be added or removed) and Object.preventExtensions() (no adding, but removing and writing are fine).

8. Serializing Objects

Serialization converts objects into a format suitable for storage or transmission. JavaScript provides JSON.stringify() for this.

Example: Serializing an Object

const person = { firstName: 'John', lastName: 'Doe', age: 30 };

const jsonPerson = JSON.stringify(person);
console.log(jsonPerson);   // {"firstName":"John","lastName":"Doe","age":30}

What JSON silently drops

JSON has fewer types than JavaScript, so a round trip is lossy:

const data = {
  when: new Date(0),
  missing: undefined,
  run() {},
  bad: NaN,
  huge: Infinity,
};

console.log(JSON.parse(JSON.stringify(data)));
// { when: '1970-01-01T00:00:00.000Z', bad: null, huge: null }

Read that carefully. The Date became a string. undefined and the function vanished entirely. NaN and Infinity became null. A Symbol value would disappear too, and a BigInt throws outright.

Circular references throw:

const c = {};
c.self = c;
JSON.stringify(c);   // TypeError: Converting circular structure to JSON

This is why JSON.parse(JSON.stringify(obj)) is a bad deep-copy. Use structuredClone().

JSON.stringify also takes two extra arguments that are worth knowing — a replacer and an indent:

JSON.stringify(person, ['firstName'], 2);
// {
//   "firstName": "John"
// }

And a class can control its own output with a toJSON() method.

9. Object Methods

a. Object.values()

Returns an array of an object's own enumerable values:

const person = { firstName: 'John', lastName: 'Doe', age: 30 };

console.log(Object.values(person));   // [ 'John', 'Doe', 30 ]

b. Object.entries()

Returns an array of [key, value] pairs:

console.log(Object.entries(person));
// [ [ 'firstName', 'John' ], [ 'lastName', 'Doe' ], [ 'age', 30 ] ]

c. Object.fromEntries()

The inverse of entries(). Together they let you map or filter an object — which you otherwise cannot do, because those are array methods:

const scores = { ganesh: 91, asha: 78, ravi: 84 };

const passed = Object.fromEntries(
  Object.entries(scores).filter(([, score]) => score >= 80)
);

console.log(passed);   // { ganesh: 91, ravi: 84 }

Entries → array method → fromEntries. That is the pattern.

d. Object.assign()

Object.assign() copies own enumerable properties from one or more sources into a target:

const info = { job: 'Developer', city: 'New York' };
const merged = Object.assign({}, person, info);
console.log(merged);
// { firstName: 'John', lastName: 'Doe', age: 30, job: 'Developer', city: 'New York' }

Later sources win on conflicts. Note the {} as the first argument — Object.assign mutates its target, so passing person directly would modify person. And like spread, this is a shallow copy.

Spread does the same job with less ceremony, and never mutates:

const merged = { ...person, ...info };

e. Object.groupBy()

Added in ES2024, for sorting a list into buckets:

const people = [
  { name: 'Ganesh', role: 'dev' },
  { name: 'Asha', role: 'ux' },
  { name: 'Ravi', role: 'dev' },
];

console.log(Object.groupBy(people, (p) => p.role));
// { dev: [ {...Ganesh}, {...Ravi} ], ux: [ {...Asha} ] }

Before this, everyone wrote the same eight-line reduce.

10. Extended Object Literal Syntax (ES6 Syntax)

Shorthand property names

When the key and the variable have the same name, write it once:

const name = 'Alice';
const age = 25;

const person = { name, age };   // same as { name: name, age: age }

console.log(person.name);   // Alice
console.log(person.age);    // 25

Shorthand methods

const counter = {
  count: 0,
  increment() {          // instead of increment: function () {}
    this.count++;
  },
};

Computed property names

Square brackets in a literal let you build a key from an expression:

const key = 'status';
const value = 'active';

const record = { [key]: value, [`${key}_at`]: Date.now() };
console.log(record.status);       // 'active'
console.log(record.status_at);    // a timestamp

Before ES6 this needed a second statement: create the object, then record[key] = value.

Destructuring

The reverse of a literal — pull properties out into variables:

const person = { firstName: 'John', lastName: 'Doe', age: 30 };

const { firstName, age } = person;
console.log(firstName, age);   // John 30

// Rename, and supply a default for a missing property
const { firstName: first, email = 'none' } = person;
console.log(first, email);     // John none

// Collect the rest
const { age: personAge, ...nameParts } = person;
console.log(nameParts);        // { firstName: 'John', lastName: 'Doe' }

Getters and setters

A property does not have to hold a value. It can run a function when read or written:

const person = {
  first: 'Ganesh',
  last: 'Jaiwal',

  get full() {
    return `${this.first} ${this.last}`;
  },

  set full(value) {
    [this.first, this.last] = value.split(' ');
  },
};

console.log(person.full);   // 'Ganesh Jaiwal'

person.full = 'A B';
console.log(person.first);  // 'A'
console.log(person.last);   // 'B'

There are no parentheses at the call site — person.full looks like a normal property, which is the point. You can turn a stored value into a computed one later without changing any code that reads it.

Use a getter for something derived from other properties. Do not put slow work behind one: anything that reads like a property should behave like a property.

Property descriptors

Every property has more to it than its value:

console.log(Object.getOwnPropertyDescriptor({ name: 'John' }, 'name'));
// { value: 'John', writable: true, enumerable: true, configurable: true }
  • writable — can the value change?

  • enumerable — does it show in Object.keys, for...in, spread and JSON.stringify?

  • configurable — can it be deleted, or its descriptor changed?

Properties you create normally get all three. Object.defineProperty lets you choose:

const record = {};
Object.defineProperty(record, 'id', {
  value: 1,
  writable: false,
  enumerable: false,
});

record.id = 999;
console.log(record.id);               // 1  — the write was ignored
console.log(Object.keys(record));     // [] — hidden from enumeration
console.log(JSON.stringify(record));  // {} — and from serialization

You will not reach for this often, but it explains something otherwise mysterious: built-in methods like Array.prototype.map are non-enumerable, which is why for...in over an array does not list every method on the prototype.

11. Summary

Objects are the data structure everything else in JavaScript is built on — arrays, functions and classes are all objects underneath.

Three things worth carrying away:

  • Objects are held by reference. === asks whether two variables point at the same object, not whether they look alike.

  • Spread, Object.assign and Object.freeze are all shallow. For a real deep copy, use structuredClone().

  • JSON.parse(JSON.stringify(obj)) is lossy. Dates become strings, functions and undefined vanish, NaN becomes null.

Next in the series: JavaScript Arrays.

Hope you like it, if yes ❤️ like & 📤share.

Thanks for your time.

Happy coding….

← JavaScript Statements JavaScript Arrays →

JavaScript - Basics to Advance

Part 9 of 12

This series will cover everything you need to know about JavaScript from basics to advanced. Please stick with us and take your JS code quality and productivity to another level #JavaScript #JS

Up next

JavaScript Arrays: A Comprehensive Guide

Last updated: September 2026 — fixed four incorrect code samples, replaced the unfulfilled follow-up promise with the actual method coverage, and added sort, at(), the ES2023 immutable methods, flatMa

More from this blog

H

Hacker's Haven

28 posts

I'm a Senior Software Developer with expertise in programming languages and tech. Follow me for detailed articles and tutorials on software development tools and frameworks.