Objects in JavaScript
Objects are collections of key-value pairs. They're the foundation of JavaScript's data structures and object-oriented programming.
📝 Creating Objects
// Object literal (most common)
let person = {
name: 'John',
age: 30,
city: 'New York'
};
// Empty object
let obj1 = {};
let obj2 = new Object();
// With computed property names
let key = 'email';
let user = {
name: 'Jane',
[key]: 'jane@example.com',
['is' + 'Active']: true
};
console.log(user); // { name: 'Jane', email: 'jane@example.com', isActive: true }
// Property shorthand (ES6)
let name = 'Bob';
let age = 25;
let person2 = { name, age }; // Same as { name: name, age: age }
// Method shorthand
let calculator = {
// Old way
add: function(a, b) {
return a + b;
},
// New way (ES6)
subtract(a, b) {
return a - b;
}
};
// Object.create()
let proto = { greet() { return 'Hello'; } };
let obj3 = Object.create(proto);
console.log(obj3.greet()); // 'Hello'
🔍 Accessing Properties
let person = {
name: 'John',
age: 30,
'favorite color': 'blue',
address: {
street: '123 Main St',
city: 'New York'
}
};
// Dot notation
console.log(person.name); // 'John'
console.log(person.age); // 30
// Bracket notation
console.log(person['name']); // 'John'
console.log(person['favorite color']); // 'blue' (required for spaces)
// Dynamic property access
let prop = 'age';
console.log(person[prop]); // 30
// Nested properties
console.log(person.address.city); // 'New York'
console.log(person['address']['city']); // 'New York'
// Optional chaining (ES2020)
let user = null;
console.log(user?.name); // undefined (no error)
let person2 = { name: 'Jane' };
console.log(person2?.address?.city); // undefined (no error)
// Non-existent property
console.log(person.email); // undefined
✏️ Modifying Objects
let person = {
name: 'John',
age: 30
};
// Add property
person.email = 'john@example.com';
person['phone'] = '555-1234';
// Modify property
person.age = 31;
person['name'] = 'John Doe';
// Delete property
delete person.phone;
console.log(person); // { name: 'John Doe', age: 31, email: '...' }
// Check if property exists
console.log('email' in person); // true
console.log('phone' in person); // false
// hasOwnProperty (own vs inherited)
console.log(person.hasOwnProperty('name')); // true
console.log(person.hasOwnProperty('toString')); // false (inherited)
// Object.hasOwn (ES2022 - safer)
console.log(Object.hasOwn(person, 'name')); // true
🔄 Iterating Over Objects
let person = {
name: 'John',
age: 30,
city: 'New York'
};
// for...in loop
for (let key in person) {
console.log(`${key}: ${person[key]}`);
}
// Object.keys() - returns array of keys
let keys = Object.keys(person);
console.log(keys); // ['name', 'age', 'city']
keys.forEach(key => {
console.log(`${key}: ${person[key]}`);
});
// Object.values() - returns array of values (ES2017)
let values = Object.values(person);
console.log(values); // ['John', 30, 'New York']
// Object.entries() - returns array of [key, value] pairs (ES2017)
let entries = Object.entries(person);
console.log(entries); // [['name', 'John'], ['age', 30], ['city', 'New York']]
entries.forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
// Convert back to object
let obj = Object.fromEntries(entries);
console.log(obj); // { name: 'John', age: 30, city: 'New York' }
📦 Object Methods
Object.assign
// Copy/merge objects
let target = { a: 1, b: 2 };
let source1 = { b: 3, c: 4 };
let source2 = { c: 5, d: 6 };
let result = Object.assign(target, source1, source2);
console.log(result); // { a: 1, b: 3, c: 5, d: 6 }
console.log(target); // target is modified!
// Clone object (shallow)
let person = { name: 'John', age: 30 };
let clone = Object.assign({}, person);
// Add properties while cloning
let extended = Object.assign({}, person, { city: 'New York' });
// Modern alternative: spread operator
let clone2 = { ...person };
let extended2 = { ...person, city: 'New York' };
Object.freeze and Object.seal
// freeze - make immutable
let person = { name: 'John', age: 30 };
Object.freeze(person);
person.age = 31; // Silently fails (error in strict mode)
person.city = 'NY'; // Silently fails
delete person.name; // Silently fails
console.log(person); // { name: 'John', age: 30 } unchanged
console.log(Object.isFrozen(person)); // true
// Note: shallow freeze only
let user = { name: 'Jane', address: { city: 'Boston' } };
Object.freeze(user);
// user.name = 'John'; // Fails
user.address.city = 'NYC'; // Works! (nested object not frozen)
// seal - prevent add/delete, allow modify
let obj = { a: 1, b: 2 };
Object.seal(obj);
obj.a = 10; // OK - can modify
obj.c = 3; // Fails - can't add
delete obj.b; // Fails - can't delete
console.log(Object.isSealed(obj)); // true
// preventExtensions - allow delete/modify, prevent add
let obj2 = { x: 1 };
Object.preventExtensions(obj2);
obj2.x = 10; // OK
delete obj2.x; // OK
obj2.y = 2; // Fails
console.log(Object.isExtensible(obj2)); // false
Object.defineProperty
// Define property with descriptors
let person = {};
Object.defineProperty(person, 'name', {
value: 'John',
writable: true, // Can be changed
enumerable: true, // Shows in for...in
configurable: true // Can be deleted
});
// Getter and setter
Object.defineProperty(person, 'age', {
value: 30,
writable: false // Read-only
});
// person.age = 31; // Fails
// With getter/setter
Object.defineProperty(person, 'fullName', {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(value) {
[this.firstName, this.lastName] = value.split(' ');
}
});
person.firstName = 'John';
person.lastName = 'Doe';
console.log(person.fullName); // 'John Doe'
person.fullName = 'Jane Smith';
console.log(person.firstName); // 'Jane'
// defineProperties - multiple at once
Object.defineProperties(person, {
email: {
value: 'john@example.com',
writable: true
},
phone: {
value: '555-1234',
writable: false
}
});
🔗 this Keyword
// this refers to the object the method belongs to
let person = {
name: 'John',
age: 30,
greet() {
console.log(`Hi, I'm ${this.name}`);
},
getAge() {
return this.age;
}
};
person.greet(); // 'Hi, I'm John'
// Arrow functions don't have their own this
let person2 = {
name: 'Jane',
greet: () => {
console.log(this.name); // undefined (this is from outer scope)
}
};
// Nested functions lose this context
let person3 = {
name: 'Bob',
friends: ['Alice', 'Charlie'],
showFriends() {
this.friends.forEach(function(friend) {
// console.log(`${this.name} knows ${friend}`); // Error!
});
}
};
// Solution 1: Arrow function
let person4 = {
name: 'Bob',
friends: ['Alice', 'Charlie'],
showFriends() {
this.friends.forEach(friend => {
console.log(`${this.name} knows ${friend}`); // Works!
});
}
};
// Solution 2: Bind
let person5 = {
name: 'Bob',
friends: ['Alice', 'Charlie'],
showFriends() {
this.friends.forEach(function(friend) {
console.log(`${this.name} knows ${friend}`);
}.bind(this));
}
};
🏗️ Constructor Functions and Prototypes
// Constructor function (before ES6 classes)
function Person(name, age) {
this.name = name;
this.age = age;
}
// Add method to prototype
Person.prototype.greet = function() {
return `Hi, I'm ${this.name}`;
};
Person.prototype.getAge = function() {
return this.age;
};
// Create instances
let john = new Person('John', 30);
let jane = new Person('Jane', 25);
console.log(john.greet()); // 'Hi, I'm John'
console.log(jane.greet()); // 'Hi, I'm Jane'
// Prototype chain
console.log(john.hasOwnProperty('name')); // true
console.log(john.hasOwnProperty('greet')); // false (on prototype)
// Check prototype
console.log(Object.getPrototypeOf(john) === Person.prototype); // true
console.log(john instanceof Person); // true
// Add property to specific instance
john.city = 'New York';
console.log(john.city); // 'New York'
console.log(jane.city); // undefined
🎓 ES6 Classes
// Modern class syntax
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hi, I'm ${this.name}`;
}
getAge() {
return this.age;
}
// Getter
get info() {
return `${this.name}, ${this.age}`;
}
// Setter
set info(value) {
[this.name, this.age] = value.split(', ');
}
// Static method (called on class, not instance)
static compare(person1, person2) {
return person1.age - person2.age;
}
}
let john = new Person('John', 30);
let jane = new Person('Jane', 25);
console.log(john.greet()); // 'Hi, I'm John'
console.log(john.info); // 'John, 30'
john.info = 'John Doe, 31';
console.log(john.name); // 'John Doe'
console.log(Person.compare(john, jane)); // 6
// Inheritance
class Student extends Person {
constructor(name, age, grade) {
super(name, age); // Call parent constructor
this.grade = grade;
}
study() {
return `${this.name} is studying`;
}
// Override parent method
greet() {
return `${super.greet()} and I'm a student`;
}
}
let student = new Student('Bob', 20, 'A');
console.log(student.greet()); // 'Hi, I'm Bob and I'm a student'
console.log(student.study()); // 'Bob is studying'
console.log(student instanceof Student); // true
console.log(student instanceof Person); // true
🔄 Object Destructuring
let person = {
name: 'John',
age: 30,
city: 'New York',
country: 'USA'
};
// Basic destructuring
let { name, age } = person;
console.log(name); // 'John'
console.log(age); // 30
// Rename variables
let { name: personName, age: personAge } = person;
console.log(personName); // 'John'
// Default values
let { name, email = 'N/A' } = person;
console.log(email); // 'N/A'
// Rest properties
let { name, ...rest } = person;
console.log(rest); // { age: 30, city: 'New York', country: 'USA' }
// Nested destructuring
let user = {
name: 'Jane',
address: {
city: 'Boston',
zip: '02101'
}
};
let { address: { city, zip } } = user;
console.log(city); // 'Boston'
// Function parameters
function greet({ name, age = 18 }) {
return `Hello ${name}, you are ${age}`;
}
console.log(greet({ name: 'John', age: 30 })); // 'Hello John, you are 30'
console.log(greet({ name: 'Jane' })); // 'Hello Jane, you are 18'
💡 Practical Examples
Deep Clone
// Shallow clone (only copies first level)
let person = {
name: 'John',
address: { city: 'NYC' }
};
let shallow = { ...person };
shallow.address.city = 'Boston';
console.log(person.address.city); // 'Boston' (both changed!)
// Deep clone with JSON (simple but limited)
let deep1 = JSON.parse(JSON.stringify(person));
// Limitations: loses functions, undefined, symbols, dates
// Deep clone with recursion
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof Array) return obj.map(item => deepClone(item));
let cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
// Modern: structuredClone (ES2022)
let deep2 = structuredClone(person);
Object Comparison
// Objects are compared by reference
let obj1 = { name: 'John' };
let obj2 = { name: 'John' };
let obj3 = obj1;
console.log(obj1 === obj2); // false (different objects)
console.log(obj1 === obj3); // true (same reference)
// Compare by value
function areEqual(obj1, obj2) {
let keys1 = Object.keys(obj1);
let keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (obj1[key] !== obj2[key]) return false;
}
return true;
}
console.log(areEqual(obj1, obj2)); // true
// Deep comparison (shallow version above)
function deepEqual(obj1, obj2) {
if (obj1 === obj2) return true;
if (typeof obj1 !== 'object' || typeof obj2 !== 'object' ||
obj1 === null || obj2 === null) {
return false;
}
let keys1 = Object.keys(obj1);
let keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) {
return false;
}
}
return true;
}
Object Factory
// Create objects with private data
function createUser(name, email) {
let isActive = true; // Private variable
return {
name,
email,
getStatus() {
return isActive ? 'Active' : 'Inactive';
},
activate() {
isActive = true;
},
deactivate() {
isActive = false;
}
};
}
let user = createUser('John', 'john@example.com');
console.log(user.name); // 'John'
console.log(user.getStatus()); // 'Active'
user.deactivate();
console.log(user.getStatus()); // 'Inactive'
// console.log(user.isActive); // undefined (private)
Object Validation
function validateUser(user) {
const required = ['name', 'email', 'age'];
const errors = [];
// Check required fields
for (let field of required) {
if (!(field in user)) {
errors.push(`${field} is required`);
}
}
// Validate email
if (user.email && !user.email.includes('@')) {
errors.push('Invalid email format');
}
// Validate age
if (user.age && (user.age < 0 || user.age > 150)) {
errors.push('Age must be between 0 and 150');
}
return {
valid: errors.length === 0,
errors
};
}
console.log(validateUser({ name: 'John', email: 'john@ex.com', age: 30 }));
// { valid: true, errors: [] }
console.log(validateUser({ name: 'Jane' }));
// { valid: false, errors: ['email is required', 'age is required'] }
Object Transform
// Transform object keys/values
function mapObject(obj, fn) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => fn(key, value))
);
}
let prices = { apple: 1.5, banana: 0.75, orange: 2 };
// Double all prices
let doubled = mapObject(prices, (key, value) => [key, value * 2]);
console.log(doubled); // { apple: 3, banana: 1.5, orange: 4 }
// Uppercase keys
let upper = mapObject(prices, (key, value) => [key.toUpperCase(), value]);
console.log(upper); // { APPLE: 1.5, BANANA: 0.75, ORANGE: 2 }
// Pick specific properties
function pick(obj, keys) {
return Object.fromEntries(
keys.filter(key => key in obj).map(key => [key, obj[key]])
);
}
let user = { name: 'John', age: 30, email: 'john@ex.com', password: '123' };
let safe = pick(user, ['name', 'email']);
console.log(safe); // { name: 'John', email: 'john@ex.com' }
// Omit properties
function omit(obj, keys) {
return Object.fromEntries(
Object.entries(obj).filter(([key]) => !keys.includes(key))
);
}
let public = omit(user, ['password']);
console.log(public); // { name: 'John', age: 30, email: 'john@ex.com' }
🎯 Key Takeaways
- Creation: Use object literal {...} for creating objects
- Properties: Access with dot (obj.key) or bracket (obj['key']) notation
- Methods: Functions as object properties, use 'this' to reference object
- Iteration: Object.keys(), Object.values(), Object.entries() for iteration
- Destructuring: Extract multiple properties in one statement
- Classes: Modern syntax for creating objects with shared methods
- Immutability: Object.freeze() prevents all modifications
- Reference type: Objects compared by reference, not value