Properties, Methods, and this Keyword

Understanding the Building Blocks of JavaScript Objects

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.

Object: house color: "blue" rooms: 4 sqft: 2000

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:

graph LR A[Property Access] --> B[Dot Notation] A --> C[Bracket Notation] B -->|person.name| D[Direct access] C -->|person['name']| E[Dynamic access] C -->|person[variable]| F[Variable-based access]
// 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

graph TD A["'this' Value"] --> B[Global Context] A --> C[Object Method] A --> D[Constructor Function] A --> E[Event Handler] A --> F[Standalone Function] A --> G[Arrow Function] B --> B1["window (browser) or global (Node.js)"] C --> C1["The object owning the method"] D --> D1["The newly created object"] E --> E1["The element that received the event"] F --> F1["window or undefined (strict mode)"] G --> G1["Inherits 'this' from surrounding scope"]

'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:

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.