Lesson 9: Working with Objects and Object Patterns
What You'll Learn
- Object destructuring
- Object methods and shorthand
- Nested objects
- Spreading and copying objects
- Common object patterns
- Object validation techniques
Why This Matters
Objects are fundamental to JavaScript. Understanding how to structure, manipulate, and validate objects will help you write cleaner, more maintainable code. In real-world applications, you'll constantly work with complex data structures represented as objects.
Part 1: Object Destructuring
Create a new file: lesson-09/object-patterns.js
Basic Destructuring
Instead of accessing properties one by one, destructure them:
const person = {
name: "Alice",
age: 30,
email: "alice@example.com"
};
// Old way
const name = person.name;
const age = person.age;
const email = person.email;
// New way (destructuring)
const { name, age, email } = person;
console.log(name); // Alice
console.log(age); // 30
console.log(email); // alice@example.com
Renaming During Destructuring
const person = {
name: "Alice",
age: 30
};
// Rename 'name' to 'personName'
const { name: personName, age: personAge } = person;
console.log(personName); // Alice
console.log(personAge); // 30
Default Values
const person = {
name: "Alice"
// age is missing
};
// Provide default value
const { name, age = 25 } = person;
console.log(name); // Alice
console.log(age); // 25 (default used)
Destructuring Function Parameters
function greetPerson({ name, age }) {
console.log(`Hello, ${name}! You are ${age} years old.`);
}
const person = { name: "Alice", age: 30 };
greetPerson(person); // Hello, Alice! You are 30 years old.
Part 2: Object Shorthand and Methods
Property Shorthand
const name = "Alice";
const age = 30;
// Old way
const person = {
name: name,
age: age
};
// New way (shorthand)
const person2 = {
name, // Same as name: name
age // Same as age: age
};
Method Shorthand
// Old way
const person = {
name: "Alice",
greet: function() {
console.log(`Hello, I'm ${this.name}`);
}
};
// New way (method shorthand)
const person2 = {
name: "Alice",
greet() {
console.log(`Hello, I'm ${this.name}`);
}
};
person2.greet(); // Hello, I'm Alice
Computed Property Names
const propertyName = "age";
const propertyValue = 30;
const person = {
name: "Alice",
[propertyName]: propertyValue // age: 30
};
console.log(person.age); // 30
Part 3: Nested Objects
Working with Nested Objects
const user = {
name: "Alice",
age: 30,
address: {
street: "123 Main St",
city: "New York",
country: "USA"
},
social: {
twitter: "@alice",
github: "alice123"
}
};
// Accessing nested properties
console.log(user.address.city); // New York
console.log(user.social.twitter); // @alice
// Destructuring nested objects
const { address: { city, country }, social: { twitter } } = user;
console.log(city); // New York
console.log(country); // USA
console.log(twitter); // @alice
Optional Chaining
Safely access nested properties that might not exist:
const user = {
name: "Alice"
// address is missing
};
// Old way - might cause error
// console.log(user.address.city); // ERROR!
// Safe way - check each level
console.log(user.address && user.address.city); // undefined
// Best way - optional chaining
console.log(user.address?.city); // undefined (no error!)
Part 4: Spreading and Copying Objects
Spread Operator
const person = {
name: "Alice",
age: 30
};
// Copy and add properties
const employee = {
...person, // Copy all properties from person
job: "Developer",
salary: 80000
};
console.log(employee);
// { name: "Alice", age: 30, job: "Developer", salary: 80000 }
Merging Objects
const defaults = {
theme: "light",
fontSize: 14,
notifications: true
};
const userPreferences = {
fontSize: 16,
notifications: false
};
// Merge (later properties override earlier ones)
const settings = {
...defaults,
...userPreferences
};
console.log(settings);
// { theme: "light", fontSize: 16, notifications: false }
Shallow vs Deep Copy
const original = {
name: "Alice",
address: {
city: "New York"
}
};
// Shallow copy (only copies top level)
const shallowCopy = { ...original };
shallowCopy.address.city = "Boston";
console.log(original.address.city); // Boston (changed!)
console.log(shallowCopy.address.city); // Boston
// Deep copy (copies all levels)
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.address.city = "Chicago";
console.log(original.address.city); // Boston (unchanged!)
console.log(deepCopy.address.city); // Chicago
Part 5: Object Factory Functions
Create objects with consistent structure:
function createPerson(name, age, email) {
return {
name,
age,
email,
greet() {
console.log(`Hello, I'm ${this.name}`);
},
getInfo() {
return ${this.name} (${this.age});
}
};
}
const alice = createPerson("Alice", 30, "alice@example.com");
const bob = createPerson("Bob", 25, "bob@example.com");
alice.greet(); // Hello, I'm Alice
console.log(bob.getInfo()); // Bob (25)
Factory with Validation
function createUser(name, age, email) {
// Validation
if (!name || typeof name !== "string") {
throw new Error("Name must be a non-empty string");
}
if (!age || age < 0 || age > 120) {
throw new Error("Age must be between 0 and 120");
}
if (!email || !email.includes("@")) {
throw new Error("Invalid email address");
}
// Return valid object
return {
name,
age,
email,
createdAt: new Date()
};
}
try {
const user = createUser("Alice", 30, "alice@example.com");
console.log(user);
} catch (error) {
console.error(error.message);
}
Part 6: Object Validation Patterns
Property Existence Checks
function validatePerson(obj) {
const requiredProps = ["name", "age", "email"];
const missing = [];
for (const prop of requiredProps) {
if (!(prop in obj)) {
missing.push(prop);
}
}
if (missing.length > 0) {
throw new Error(Missing properties: ${missing.join(", ")});
}
return true;
}
const person = { name: "Alice", age: 30 };
// validatePerson(person); // Error: Missing properties: email
Type Validation
function validateUser(user) {
if (typeof user.name !== "string") {
throw new Error("Name must be a string");
}
if (typeof user.age !== "number" || user.age < 0) {
throw new Error("Age must be a positive number");
}
if (typeof user.email !== "string" || !user.email.includes("@")) {
throw new Error("Invalid email");
}
return true;
}
Schema Validation Function
function validateSchema(obj, schema) {
for (const [key, validator] of Object.entries(schema)) {
if (!(key in obj)) {
throw new Error(Missing property: ${key});
}
if (!validator(obj[key])) {
throw new Error(Invalid value for ${key});
}
}
return true;
}
// Define schema
const userSchema = {
name: (value) => typeof value === "string" && value.length > 0,
age: (value) => typeof value === "number" && value > 0 && value < 120,
email: (value) => typeof value === "string" && value.includes("@")
};
// Validate
const user = { name: "Alice", age: 30, email: "alice@example.com" };
validateSchema(user, userSchema); // Pass!
const invalidUser = { name: "", age: -5, email: "invalid" };
// validateSchema(invalidUser, userSchema); // Error!
Part 7: Common Object Patterns
Options Object Pattern
function createWindow(options = {}) {
// Provide defaults
const defaults = {
width: 800,
height: 600,
title: "New Window",
resizable: true
};
// Merge with user options
const config = { ...defaults, ...options };
console.log(`Creating window: ${config.title}`);
console.log(`Size: ${config.width}x${config.height}`);
console.log(`Resizable: ${config.resizable}`);
}
createWindow(); // Uses all defaults
createWindow({ title: "My App", width: 1024 }); // Override some
Builder Pattern
class QueryBuilder {
constructor() {
this.query = {};
}
select(fields) {
this.query.select = fields;
return this; // Return this for chaining
}
from(table) {
this.query.from = table;
return this;
}
where(condition) {
this.query.where = condition;
return this;
}
build() {
return this.query;
}
}
const query = new QueryBuilder()
.select(["name", "age"])
.from("users")
.where("age > 18")
.build();
console.log(query);
// { select: ["name", "age"], from: "users", where: "age > 18" }
Practice Exercises
Exercise 1: User Profile
Create a factory function that creates user profiles with validation.
Exercise 2: Settings Manager
Build a settings object with defaults, merging, and validation.
Exercise 3: Data Transformer
Write a function that transforms nested objects from one structure to another.
Exercise 4: Object Validator
Create a reusable validator that can validate any object against a schema.
Key Concepts Summary
| Concept | Purpose | Example |
|---|---|---|
| Destructuring | Extract properties easily | const { name, age } = person |
| Spread | Copy/merge objects | { ...obj1, ...obj2 } |
| Optional chaining | Safe nested access | obj?.prop?.nested |
| Factory function | Create objects consistently | createUser(name, age) |
| Validation | Ensure data integrity | validateSchema(obj, schema) |
What You Learned
- ✅ How to destructure objects efficiently
- ✅ Object shorthand and method syntax
- ✅ Working with nested objects safely
- ✅ Copying and merging objects
- ✅ Creating factory functions with validation
- ✅ Common object patterns for real applications
What's Next?
In the next lesson, you'll learn about array methods - powerful tools for transforming, filtering, and processing arrays of data!