JavaScript Objects: A Comprehensive Guide with ES6 Syntax

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 andJSON.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.assignandObject.freezeare all shallow. For a real deep copy, usestructuredClone().JSON.parse(JSON.stringify(obj))is lossy. Dates become strings, functions andundefinedvanish,NaNbecomesnull.
Next in the series: JavaScript Arrays.
Hope you like it, if yes ❤️ like & 📤share.
Thanks for your time.
Happy coding….





