Object Creation and Literals

Understanding JavaScript's Object System

What are JavaScript Objects?

In JavaScript, objects are the fundamental building blocks of the language. They're collections of related data and functionality, stored as key-value pairs called properties and methods.

Think of an object like a backpack - it can hold various items (properties), and it can have different functions (methods) like opening, closing, or adjusting straps. Just as a backpack organizes your personal items, objects organize related data and behavior.

Basic Object Structure

// Property: A key-value pair
// Method: A function stored as a property value

const backpack = {
    // Properties
    color: "blue",
    volume: 30,
    pockets: 5,
    
    // Methods
    open: function() {
        console.log("Backpack is now open");
    },
    close: function() {
        console.log("Backpack is now closed");
    }
};

Ways to Create Objects

JavaScript provides several ways to create objects, each with its own use cases and advantages.

graph TD A[JavaScript Object Creation] --> B[Object Literals] A --> C[Object Constructor] A --> D[Object.create Method] A --> E[Factory Functions] A --> F[Constructor Functions] A --> G[ES6 Classes]

Object Literals

The simplest and most common way to create objects is using object literals, which use curly braces.

const car = {
    make: "Toyota",
    model: "Corolla",
    year: 2023,
    isElectric: false,
    start: function() {
        console.log("Engine started!");
    }
};

Object literals are ideal for one-off objects or configuration objects that won't need multiple instances.

The Object Constructor

You can use the built-in Object constructor function with the new keyword.

const person = new Object();
person.name = "Maria";
person.age = 28;
person.greet = function() {
    console.log(`Hello, my name is ${this.name}`);
};

This approach is less common but can be useful when you need to create an object dynamically.

Object.create() Method

The Object.create() method creates a new object with the specified prototype object.

const vehiclePrototype = {
    start: function() {
        console.log("Engine started!");
    },
    stop: function() {
        console.log("Engine stopped!");
    }
};

const myCar = Object.create(vehiclePrototype);
myCar.make = "Honda";
myCar.model = "Civic";

myCar.start(); // "Engine started!"

This method is powerful for implementing prototypal inheritance.

Factory Functions

Factory functions are regular functions that return new objects. They're a pattern for creating multiple similar objects without using the new keyword.

function createUser(name, email, role) {
    return {
        name,
        email,
        role,
        createdAt: new Date(),
        isActive: true,
        login() {
            console.log(`${this.name} has logged in`);
        },
        logout() {
            console.log(`${this.name} has logged out`);
        }
    };
}

const user1 = createUser("Alice", "alice@example.com", "admin");
const user2 = createUser("Bob", "bob@example.com", "editor");

user1.login(); // "Alice has logged in"

Factory functions are great for creating multiple objects with privacy through closures. Notice we're using the ES6 shorthand property names where the variable name becomes the property name.

Constructor Functions

Constructor functions are used with the new keyword and follow the convention of starting with a capital letter.

function Product(name, price, category) {
    this.name = name;
    this.price = price;
    this.category = category;
    this.isInStock = true;
    
    this.displayInfo = function() {
        console.log(`${this.name} - $${this.price} (${this.category})`);
    };
}

const laptop = new Product("MacBook Pro", 1299, "Electronics");
const chair = new Product("Ergonomic Chair", 249, "Furniture");

laptop.displayInfo(); // "MacBook Pro - $1299 (Electronics)"

When you use the new keyword with a constructor function, several things happen:

  1. A new empty object is created
  2. The function is called with this set to the new object
  3. The new object is linked to the constructor's prototype
  4. The function implicitly returns the object (unless it explicitly returns something else)

ES6 Classes

ES6 introduced class syntax, which is syntactic sugar over JavaScript's existing prototype-based inheritance.

class Animal {
    constructor(name, species) {
        this.name = name;
        this.species = species;
        this.createdAt = new Date();
    }
    
    makeSound() {
        console.log("Some generic animal sound");
    }
    
    describe() {
        console.log(`${this.name} is a ${this.species}`);
    }
}

const dog = new Animal("Rex", "dog");
dog.describe(); // "Rex is a dog"

Classes provide a cleaner, more familiar syntax for creating objects and implementing inheritance, especially for developers coming from class-based languages.

Real-World Object Usage Examples

E-commerce Product System

class Product {
    constructor(id, name, price) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.inventory = 0;
    }
    
    applyDiscount(percentage) {
        return this.price * (1 - percentage/100);
    }
    
    updateInventory(amount) {
        this.inventory += amount;
        return this.inventory;
    }
}

class DigitalProduct extends Product {
    constructor(id, name, price, fileSize) {
        super(id, name, price);
        this.fileSize = fileSize;
        this.inventory = Infinity; // Digital products have unlimited inventory
    }
    
    generateDownloadLink() {
        return `https://example.com/downloads/${this.id}`;
    }
}

// Usage
const physicalBook = new Product(101, "JavaScript: The Good Parts", 29.99);
physicalBook.updateInventory(50);

const ebook = new DigitalProduct(102, "JavaScript: The Good Parts (Digital)", 19.99, "4.2MB");
console.log(ebook.generateDownloadLink()); // "https://example.com/downloads/102"
console.log(`Sale price: $${ebook.applyDiscount(15).toFixed(2)}`); // "Sale price: $16.99"

User Authentication System

class User {
    constructor(username, email) {
        this.username = username;
        this.email = email;
        this.lastLogin = null;
        this._isLoggedIn = false;
        this._passwordHash = null;
    }
    
    login(password) {
        // In real apps, you'd verify the password hash
        if (this._passwordHash && this._verifyPassword(password)) {
            this._isLoggedIn = true;
            this.lastLogin = new Date();
            console.log(`${this.username} logged in successfully`);
            return true;
        }
        console.log("Login failed");
        return false;
    }
    
    logout() {
        if (this._isLoggedIn) {
            this._isLoggedIn = false;
            console.log(`${this.username} logged out`);
        }
    }
    
    setPassword(password) {
        // In real apps, you'd hash the password
        this._passwordHash = `hashed_${password}`;
    }
    
    _verifyPassword(password) {
        // Simplified password verification
        return this._passwordHash === `hashed_${password}`;
    }
    
    get loginStatus() {
        return this._isLoggedIn ? "Online" : "Offline";
    }
}

// Usage
const alice = new User("alice", "alice@example.com");
alice.setPassword("securePassword123");
alice.login("securePassword123"); // "alice logged in successfully"
console.log(alice.loginStatus); // "Online"
alice.logout(); // "alice logged out"

Dynamic Property Access

JavaScript allows dynamic access and modification of object properties:

const settings = {
    theme: "dark",
    fontSize: 16,
    notifications: true
};

// Accessing with dot notation
console.log(settings.theme); // "dark"

// Accessing with bracket notation
const property = "fontSize";
console.log(settings[property]); // 16

// Adding properties dynamically
settings.language = "English";
settings["timeZone"] = "UTC-5";

// Deleting properties
delete settings.notifications;

Bracket notation is especially powerful for dynamic property access based on variables or expressions.

Object Property Descriptors

JavaScript objects have property descriptors that control how properties behave:

const user = {};

Object.defineProperty(user, 'name', {
    value: 'John',
    writable: true,     // Can the value be changed?
    enumerable: true,   // Will it show up in for...in loops?
    configurable: true  // Can this property be deleted or its attributes modified?
});

Object.defineProperty(user, 'id', {
    value: 12345,
    writable: false,    // Read-only property
    enumerable: false,  // Won't show up in Object.keys() or for...in
    configurable: false // Can't be deleted or reconfigured
});

// Trying to modify the read-only property
user.id = 54321;        // This won't work (in strict mode, throws an error)
console.log(user.id);   // Still 12345

Property descriptors allow fine-grained control over how properties behave.

Practice Activities

Activity 1: Create a Book Manager

Create an object-oriented system to manage a collection of books. Implement classes or constructor functions for books and a library to manage them.

Activity 2: Extend Built-in Objects

Extend JavaScript's built-in Array object with helpful methods like first(), last(), and random() using the prototype.

Activity 3: Bank Account System

Implement a bank account system with different account types (checking, savings) that share common behaviors but have unique properties.

Summary

In this lecture, we've explored:

Objects are fundamental to JavaScript programming, and understanding how to create and manipulate them is essential for building modern web applications.