JavaScript Objects

JavaScript objects are one of the core concepts of the language. They are a composite data type used to store collections of key-value pairs and more complex entities. Objects are one of the most powerful features in JavaScript, and almost all JavaScript values (except primitives) are objects. In this chapter, we will learn in depth about various aspects of JavaScript objects.

What is an Object

An object is a collection of properties, where each property has a key (also called property name) and a value. Values can be primitive values (such as strings, numbers, booleans) or other objects, or even functions (called methods).

// Create a simple object
const person = {
    name: "John",
    age: 25,
    isStudent: true
};

Ways to Create Objects

const person = {
    name: "John",
    age: 25,
    greet: function() {
        return "Hello, I'm " + this.name;
    }
};

2. Using new Object()

const person = new Object();
person.name = "Jane";
person.age = 30;
person.greet = function() {
    return "Hello, I'm " + this.name;
};

3. Using Constructor Function

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.greet = function() {
        return "Hello, I'm " + this.name;
    };
}

const person1 = new Person("Alice", 28);

4. Using Object.create()

const personPrototype = {
    greet: function() {
        return "Hello, I'm " + this.name;
    }
};

const person = Object.create(personPrototype);
person.name = "Bob";
person.age = 35;

Accessing and Setting Properties

Dot Notation

const person = {
    name: "John",
    age: 25
};

// Access properties
console.log(person.name); // "John"
console.log(person.age);  // 25

// Set properties
person.age = 26;
person.city = "New York"; // Add new property

Bracket Notation

const person = {
    name: "Jane",
    age: 30
};

// Access properties
console.log(person["name"]); // "Jane"
console.log(person["age"]);  // 30

// Set properties
person["age"] = 31;
person["city"] = "Los Angeles"; // Add new property

// Dynamic property names
const propertyName = "hobby";
person[propertyName] = "reading";

Special Property Names

const obj = {
    "first name": "John",
    "last-name": "Doe",
    123: "numeric key",
    true: "boolean key"
};

console.log(obj["first name"]); // "John"
console.log(obj["last-name"]);  // "Doe"
console.log(obj[123]);          // "numeric key"
console.log(obj[true]);         // "boolean key"

Deleting Properties

const person = {
    name: "John",
    age: 25,
    city: "New York"
};

// Delete property
delete person.city;
console.log(person.city); // undefined

// Deleting non-existent property doesn't throw error
delete person.nonExistent; // Returns true

Object Methods

Functions in objects are called methods:

const calculator = {
    add: function(a, b) {
        return a + b;
    },
    
    subtract: function(a, b) {
        return a - b;
    },
    
    // ES6 shorthand method
    multiply(a, b) {
        return a * b;
    },
    
    divide(a, b) {
        if (b !== 0) {
            return a / b;
        } else {
            return "Cannot divide by zero";
        }
    }
};

console.log(calculator.add(5, 3));      // 8
console.log(calculator.multiply(4, 2));  // 8

The this Keyword

In object methods, the this keyword refers to the object that called the method:

const person = {
    name: "John",
    age: 25,
    
    introduce: function() {
        return "I'm " + this.name + ", " + this.age + " years old";
    },
    
    growOlder: function() {
        this.age++;
        return this.age;
    }
};

console.log(person.introduce()); // "I'm John, 25 years old"
console.log(person.growOlder()); // 26

Iterating Over Objects

for...in Loop

const person = {
    name: "John",
    age: 25,
    city: "New York"
};

for (let key in person) {
    console.log(key + ": " + person[key]);
}
// Output:
// name: John
// age: 25
// city: New York

Object.keys()

const person = {
    name: "Jane",
    age: 30,
    city: "Los Angeles"
};

const keys = Object.keys(person);
console.log(keys); // ["name", "age", "city"]

keys.forEach(key => {
    console.log(key + ": " + person[key]);
});

Object.values()

const person = {
    name: "Alice",
    age: 28,
    city: "Chicago"
};

const values = Object.values(person);
console.log(values); // ["Alice", 28, "Chicago"]

Object.entries()

const person = {
    name: "Bob",
    age: 35,
    city: "Miami"
};

const entries = Object.entries(person);
console.log(entries); 
// [["name", "Bob"], ["age", 35], ["city", "Miami"]]

entries.forEach(([key, value]) => {
    console.log(key + ": " + value);
});

Property Descriptors

JavaScript allows fine-grained control over object properties:

const obj = {};

// Define property
Object.defineProperty(obj, "name", {
    value: "John",
    writable: false,    // Not writable
    enumerable: true,   // Enumerable
    configurable: false // Not configurable
});

console.log(obj.name); // "John"
obj.name = "Jane";     // Invalid, but throws error in strict mode
console.log(obj.name); // Still "John"

Freezing and Sealing Objects

Object.freeze()

const obj = {
    name: "John",
    age: 25
};

Object.freeze(obj);

// All these operations are invalid
obj.age = 30;        // Invalid
obj.city = "NYC";    // Invalid
delete obj.name;     // Invalid

console.log(obj); // { name: "John", age: 25 }

Object.seal()

const obj = {
    name: "Jane",
    age: 30
};

Object.seal(obj);

// Can modify existing properties
obj.age = 31;        // Valid

// But cannot add or delete properties
obj.city = "LA";     // Invalid
delete obj.name;     // Invalid

console.log(obj); // { name: "Jane", age: 31 }

Nested Objects

Objects can contain other objects:

const company = {
    name: "Tech Corp",
    address: {
        street: "123 Main St",
        city: "New York",
        zipCode: "10001"
    },
    employees: [
        {
            name: "John",
            position: "Engineer",
            contact: {
                email: "john@example.com",
                phone: "123-456-7890"
            }
        },
        {
            name: "Jane",
            position: "Designer",
            contact: {
                email: "jane@example.com",
                phone: "098-765-4321"
            }
        }
    ]
};

// Access nested properties
console.log(company.address.city); // "New York"
console.log(company.employees[0].contact.email); // "john@example.com"

Object Copying

Shallow Copy

const original = {
    name: "John",
    age: 25,
    hobbies: ["reading", "swimming"]
};

// Using Object.assign()
const copy1 = Object.assign({}, original);

// Using spread operator (ES6)
const copy2 = { ...original };

// Problem with shallow copy: nested objects are still references
copy1.name = "Jane";
copy1.hobbies.push("running");

console.log(original.name);    // "John" (unchanged)
console.log(original.hobbies); // ["reading", "swimming", "running"] (changed!)

Deep Copy

const original = {
    name: "John",
    age: 25,
    hobbies: ["reading", "swimming"],
    address: {
        city: "New York",
        street: "Main St"
    }
};

// Using JSON methods (with limitations)
const deepCopy = JSON.parse(JSON.stringify(original));

deepCopy.name = "Jane";
deepCopy.hobbies.push("running");
deepCopy.address.city = "Los Angeles";

console.log(original.name);     // "John" (unchanged)
console.log(original.hobbies);  // ["reading", "swimming"] (unchanged)
console.log(original.address.city); // "New York" (unchanged)

// Note: JSON methods cannot copy functions, undefined, Symbol, etc.

Object Merging

const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const obj3 = { c: 5, d: 6 };

// Using Object.assign()
const merged1 = Object.assign({}, obj1, obj2, obj3);
console.log(merged1); // { a: 1, b: 3, c: 5, d: 6 }

// Using spread operator
const merged2 = { ...obj1, ...obj2, ...obj3 };
console.log(merged2); // { a: 1, b: 3, c: 5, d: 6 }

Object Destructuring

const person = {
    name: "John",
    age: 25,
    city: "New York",
    job: "Engineer"
};

// Basic destructuring
const { name, age } = person;
console.log(name); // "John"
console.log(age);  // 25

// Renaming variables
const { name: fullName, age: years } = person;
console.log(fullName); // "John"
console.log(years);    // 25

// Default values
const { name: n, salary = 5000 } = person;
console.log(n);      // "John"
console.log(salary); // 5000

// Nested destructuring
const company = {
    name: "Tech Corp",
    address: {
        city: "New York",
        street: "Main St"
    }
};

const { address: { city } } = company;
console.log(city); // "New York"

Computed Property Names

const propertyName = "dynamicProperty";
const value = "dynamic value";

// ES6 computed property names
const obj = {
    [propertyName]: value,
    [1 + 2]: "computed result"
};

console.log(obj.dynamicProperty); // "dynamic value"
console.log(obj[3]);              // "computed result"

Object Methods

Object.keys(), Object.values(), Object.entries()

const person = {
    name: "John",
    age: 25,
    city: "New York"
};

console.log(Object.keys(person));    // ["name", "age", "city"]
console.log(Object.values(person));  // ["John", 25, "New York"]
console.log(Object.entries(person)); // [["name", "John"], ["age", 25], ["city", "New York"]]

Object.hasOwnProperty()

const person = {
    name: "John",
    age: 25
};

console.log(person.hasOwnProperty("name"));     // true
console.log(person.hasOwnProperty("toString")); // false

Object.is()

console.log(Object.is(1, 1));        // true
console.log(Object.is(0, -0));       // false
console.log(Object.is(NaN, NaN));    // true
console.log(Object.is(null, null));  // true

Practical Example

// User management system
const userSystem = {
    users: [],
    
    addUser(user) {
        this.users.push({
            id: Date.now(),
            ...user,
            createdAt: new Date()
        });
    },
    
    findUserById(id) {
        return this.users.find(user => user.id === id);
    },
    
    updateUser(id, updates) {
        const user = this.findUserById(id);
        if (user) {
            Object.assign(user, updates);
            return user;
        }
        return null;
    },
    
    deleteUser(id) {
        const index = this.users.findIndex(user => user.id === id);
        if (index !== -1) {
            return this.users.splice(index, 1)[0];
        }
        return null;
    },
    
    listUsers() {
        return this.users.map(user => ({
            id: user.id,
            name: user.name,
            email: user.email
        }));
    }
};

// Usage example
userSystem.addUser({ name: "John", email: "john@example.com" });
userSystem.addUser({ name: "Jane", email: "jane@example.com" });

console.log(userSystem.listUsers());

Summary

Key points about JavaScript objects:

  1. Creation Methods: Object literal, new Object(), constructor function, Object.create()
  2. Property Access: Dot notation, bracket notation
  3. Method Definition: Regular function, ES6 shorthand method
  4. this Keyword: Refers to the calling object
  5. Iteration Methods: for...in, Object.keys(), Object.values(), Object.entries()
  6. Property Control: Property descriptors, freeze, seal
  7. Copy Operations: Shallow copy, deep copy
  8. Modern Features: Destructuring, spread operator, computed property names

Objects are the foundation of JavaScript programming, and mastering their use is crucial for writing complex JavaScript applications. In the next chapter, we will learn about JavaScript conditional statements.