Object Properties in Depth
Properties are the values associated with an object. Think of properties as the characteristics or attributes that describe an object - like how a house has characteristics such as its color, number of rooms, or square footage.
Property Types
JavaScript object properties can store different types of values:
const person = {
// Primitive values
name: "Sarah", // String
age: 32, // Number
isEmployed: true, // Boolean
favoriteColor: null, // Null
// Complex values
hobbies: ["reading", "hiking", "cooking"], // Array
address: { // Nested object
street: "123 Main St",
city: "Springfield",
zipCode: "12345"
},
// Function (Method)
greet: function() {
return `Hello, my name is ${this.name}`;
}
};
Property Access and Manipulation
Accessing Properties
There are two primary ways to access object properties:
// Dot notation
console.log(person.name); // "Sarah"
console.log(person.address.city); // "Springfield"
// Bracket notation
console.log(person["age"]); // 32
// Dynamic access with variables
const propertyName = "hobbies";
console.log(person[propertyName]); // ["reading", "hiking", "cooking"]
// When property names have spaces or special characters, you must use bracket notation
const product = {
"product id": "A12345",
"shipping-info": {
carrier: "FedEx",
method: "Ground"
}
};
console.log(product["product id"]); // "A12345"
console.log(product["shipping-info"].carrier); // "FedEx"
Adding and Modifying Properties
JavaScript objects are dynamic, allowing properties to be added or modified at any time:
const car = {
make: "Honda",
model: "Civic",
year: 2020
};
// Adding new properties
car.color = "blue";
car["fuelType"] = "gasoline";
// Modifying existing properties
car.year = 2021;
// Adding a nested object
car.features = {
sunroof: true,
navigation: true,
bluetooth: true
};
console.log(car);
/* Result:
{
make: "Honda",
model: "Civic",
year: 2021,
color: "blue",
fuelType: "gasoline",
features: {
sunroof: true,
navigation: true,
bluetooth: true
}
}
*/
Removing Properties
// Delete operator removes a property
delete car.color;
// Setting to undefined keeps the property but removes its value
car.fuelType = undefined;
console.log("color" in car); // false (property gone)
console.log("fuelType" in car); // true (property exists but is undefined)
Object Methods
Methods are functions stored as object properties. They define the behaviors or actions that an object can perform. If properties are what an object is, methods are what an object can do.
Think of a calculator: its properties might be the brand, model, and power source, while its methods are the operations it can perform (add, subtract, etc.).
Method Syntax Variations
const calculator = {
// Method using function expression (traditional syntax)
add: function(a, b) {
return a + b;
},
// Method using shorthand syntax (ES6)
subtract(a, b) {
return a - b;
},
// Arrow function as method (behaves differently with 'this')
multiply: (a, b) => a * b,
// Method that uses other methods
calculate(operation, a, b) {
switch(operation) {
case 'add': return this.add(a, b);
case 'subtract': return this.subtract(a, b);
case 'multiply': return this.multiply(a, b);
default: return 'Unknown operation';
}
}
};
Method Invocation
console.log(calculator.add(5, 3)); // 8
console.log(calculator.subtract(10, 4)); // 6
console.log(calculator.calculate('multiply', 4, 3)); // 12
The 'this' Keyword
The this keyword is one of JavaScript's most powerful yet confusing features. It refers to the context in which a function is executed.
Think of this as "self-awareness" for an object. It's like saying "me" or "myself" in human language - it refers to the entity currently taking action.
The Value of 'this' Depends on Context
'this' in Object Methods
When a method is called on an object, this refers to the object itself:
const user = {
firstName: "John",
lastName: "Doe",
fullName: function() {
return this.firstName + " " + this.lastName;
// 'this' refers to the user object
},
updateName: function(newFirstName) {
this.firstName = newFirstName;
// 'this' updates the user object's firstName property
}
};
console.log(user.fullName()); // "John Doe"
user.updateName("Jane");
console.log(user.fullName()); // "Jane Doe"
This is incredibly powerful because it allows methods to access and manipulate the object's own properties - making objects self-contained units of state and behavior.
Common 'this' Pitfalls
The value of this can change depending on how a function is called:
const user = {
name: "Alice",
greet: function() {
console.log(`Hello, my name is ${this.name}`);
}
};
// Method invocation - 'this' refers to user object
user.greet(); // "Hello, my name is Alice"
// Function reference - 'this' binding is lost
const greetFunction = user.greet;
greetFunction(); // "Hello, my name is undefined"
// The problem occurs because when the function is called directly,
// 'this' no longer refers to the user object
Solutions to 'this' Binding Issues
// Solution 1: Use bind() to explicitly set 'this'
const boundGreet = user.greet.bind(user);
boundGreet(); // "Hello, my name is Alice"
// Solution 2: Arrow functions inherit 'this' from their surrounding scope
const user2 = {
name: "Bob",
// Traditional function for the method
delayedGreet: function() {
// Arrow function for the callback preserves 'this'
setTimeout(() => {
console.log(`Hello, my name is ${this.name}`);
}, 1000);
}
};
user2.delayedGreet(); // After 1 second: "Hello, my name is Bob"
// Solution 3: Store 'this' in a variable
const user3 = {
name: "Charlie",
delayedGreet: function() {
// Store 'this' in a variable (common pattern in pre-ES6 code)
const self = this;
setTimeout(function() {
console.log(`Hello, my name is ${self.name}`);
}, 1000);
}
};
user3.delayedGreet(); // After 1 second: "Hello, my name is Charlie"
Arrow Functions and 'this'
Arrow functions behave differently with this - they don't have their own this binding but inherit it from the surrounding scope.
// Traditional function method vs. arrow function method
const counter = {
count: 0,
// Traditional function as method
incrementTraditional: function() {
this.count++; // 'this' refers to the counter object
return this.count;
},
// Arrow function as method
incrementArrow: () => {
this.count++; // 'this' refers to the surrounding scope (window/global)
return this.count;
}
};
console.log(counter.incrementTraditional()); // 1
console.log(counter.incrementArrow()); // NaN (this.count is undefined)
// AVOID using arrow functions for object methods when you need to access the object with 'this'
However, arrow functions are perfect for callbacks inside methods:
const inventory = {
products: ["Laptop", "Phone", "Tablet"],
showProducts: function() {
// Using traditional function - 'this' would be undefined
// this.products.forEach(function(product) {
// console.log(product); // Error: Cannot read property 'forEach' of undefined
// });
// Using arrow function - 'this' refers to the inventory object
this.products.forEach(product => {
console.log(`${product} is in stock at ${this.store}`);
});
},
store: "Electronics Store"
};
inventory.showProducts();
// "Laptop is in stock at Electronics Store"
// "Phone is in stock at Electronics Store"
// "Tablet is in stock at Electronics Store"
Practical Applications
Building a Shopping Cart
const shoppingCart = {
items: [],
total: 0,
addItem(name, price, quantity = 1) {
const item = {
id: Date.now().toString(),
name,
price,
quantity
};
this.items.push(item);
this.calculateTotal();
console.log(`Added ${quantity} ${name}(s) to cart`);
},
removeItem(id) {
const index = this.items.findIndex(item => item.id === id);
if (index !== -1) {
const removedItem = this.items[index];
this.items.splice(index, 1);
this.calculateTotal();
console.log(`Removed ${removedItem.name} from cart`);
}
},
updateQuantity(id, newQuantity) {
const item = this.items.find(item => item.id === id);
if (item) {
item.quantity = newQuantity;
this.calculateTotal();
console.log(`Updated ${item.name} quantity to ${newQuantity}`);
}
},
calculateTotal() {
this.total = this.items.reduce((sum, item) => {
return sum + (item.price * item.quantity);
}, 0);
},
checkout() {
if (this.items.length === 0) {
console.log("Your cart is empty");
return;
}
console.log("=== Order Summary ===");
this.items.forEach(item => {
console.log(`${item.name} x${item.quantity}: $${(item.price * item.quantity).toFixed(2)}`);
});
console.log("===================");
console.log(`Total: $${this.total.toFixed(2)}`);
// In a real app, you would process payment here
this.items = [];
this.total = 0;
console.log("Thank you for your purchase!");
}
};
// Usage example
shoppingCart.addItem("Laptop", 999.99);
shoppingCart.addItem("Mouse", 29.99, 2);
shoppingCart.checkout();
Media Player Interface
const mediaPlayer = {
media: null,
isPlaying: false,
volume: 50,
playbackSpeed: 1.0,
loadMedia(mediaSource) {
this.media = {
source: mediaSource,
type: mediaSource.split('.').pop(), // Get file extension
duration: Math.floor(Math.random() * 300) // Simulated duration in seconds
};
console.log(`Media loaded: ${mediaSource}`);
this.displayMediaInfo();
},
play() {
if (!this.media) {
console.log("No media loaded");
return;
}
this.isPlaying = true;
console.log(`▶️ Playing: ${this.media.source}`);
},
pause() {
if (this.isPlaying) {
this.isPlaying = false;
console.log("⏸️ Paused");
}
},
stop() {
if (this.isPlaying) {
this.isPlaying = false;
console.log("⏹️ Stopped");
}
},
setVolume(level) {
if (level >= 0 && level <= 100) {
this.volume = level;
console.log(`Volume set to ${level}%`);
} else {
console.log("Volume must be between 0 and 100");
}
},
setPlaybackSpeed(speed) {
if (speed >= 0.25 && speed <= 2) {
this.playbackSpeed = speed;
console.log(`Playback speed set to ${speed}x`);
} else {
console.log("Playback speed must be between 0.25 and 2");
}
},
displayMediaInfo() {
if (!this.media) return;
const formatTime = seconds => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}:${secs.toString().padStart(2, '0')}`;
};
console.log("=== Media Info ===");
console.log(`File: ${this.media.source}`);
console.log(`Type: ${this.media.type}`);
console.log(`Duration: ${formatTime(this.media.duration)}`);
console.log("=================");
}
};
// Usage example
mediaPlayer.loadMedia("vacation_video.mp4");
mediaPlayer.setVolume(75);
mediaPlayer.play();
mediaPlayer.setPlaybackSpeed(1.5);
mediaPlayer.pause();
Computed Property Names
ES6 introduced computed property names, allowing you to use expressions for property names:
const propertyPrefix = "user";
const propertyNumber = 42;
const dynamicObject = {
[propertyPrefix + propertyNumber]: "John",
[`${propertyPrefix}Email`]: "john@example.com",
[`computed_${Math.random().toString(36).substr(2, 5)}`]: "Random property"
};
console.log(dynamicObject.user42); // "John"
console.log(dynamicObject.userEmail); // "john@example.com"
// The third property name is random and would need to be accessed via Object.keys
This feature is particularly useful for creating dynamic objects based on variables or expressions.
Property Value Shorthand
When a property name matches a variable name, you can use the shorthand syntax:
const name = "Sarah";
const age = 32;
const profession = "Developer";
// Old way
const person1 = {
name: name,
age: age,
profession: profession
};
// ES6 shorthand
const person2 = {
name,
age,
profession
};
console.log(person2); // {name: "Sarah", age: 32, profession: "Developer"}
Practice Activities
Activity 1: Create a Task Manager
Implement a task manager object with methods to add, complete, and list tasks. Use the this keyword to manage task state.
Activity 2: Fix 'this' Context Bugs
Debug a series of functions where the this context is lost and fix them using bind, arrow functions, or other techniques.
Activity 3: Build a Calculator Object
Create a calculator object with methods for basic operations and a memory function. Ensure the memory is maintained within the object using this.
Summary
In this lecture, we've explored:
- Object properties - how to define, access, and manipulate them
- Object methods - functions attached to objects that define behavior
- The
thiskeyword - how it provides self-reference within objects - Common pitfalls with
thisand solutions like bind() and arrow functions - ES6 features like computed property names and property value shorthand
- Real-world examples showing objects with properties and methods working together
Understanding these concepts is crucial for effective object-oriented programming in JavaScript, allowing you to create well-structured, maintainable code with clear separation of concerns.