Chapter 7: JavaScript Part 2

Take your JavaScript skills to the next level with advanced array methods, object transformations, closures, promises, and DOM integration. This chapter builds on the fundamentals to teach you professional-level JavaScript patterns.

What you'll learn:
  • Problem-solving mindset and debugging strategies
  • Advanced array methods: splice, includes, building new arrays
  • Object transformations and grouping patterns
  • Constructor functions and the this keyword
  • Closures and function chaining
  • Promises and asynchronous JavaScript
  • DOM events and form validation

7.1 The Problem-Solving Mindset

Before diving into advanced JavaScript, let's develop the thinking process that separates beginners from effective programmers.

The 4-Step Approach

When you see a coding problem, don't start typing immediately. Follow this process:

Step 1: Understand the Problem

Ask yourself: What is the input? What is the expected output? What transformation happens between them?
Skipping this often leads to the "XY Problem"—solving the wrong issue entirely.

Step 2: Plan Your Approach

Before writing code, describe your solution in plain English. "For each item, I will check if..."
Writing out logic first separates thinking from syntax. It's faster to edit English than debug code.

Step 3: Write the Code

Translate your plan into JavaScript. Start with small, testable pieces rather than the whole function at once.

Step 4: Debug & Refine

If it doesn't work, use console.log() to see the data at each step. Don't guess—verify.

Example: Thinking Through a Problem

Let's apply this to Exercise 31: "Filter an array in-place to keep only values between a and b."

// Step 1: Understand
// Input: array [5, 3, 8, 1], a=1, b=4
// Output: modify array to become [3, 1]
// Transformation: REMOVE elements outside range

// Step 2: Plan
// - Loop through array
// - For each element, check if it's outside range
// - If outside, remove it (splice)
// - Key insight: Modifying an array while looping through it causes index shifts.
//   Solution: Loop BACKWARDS to avoid skipping elements!

Quick Quiz: What should you do FIRST when facing a coding problem?

A Start typing code immediately
B Understand input, output, and transformation
C Copy code from the internet
D Ask someone else to solve it

7.2 Advanced Array Methods

In Part 1, you learned to loop through arrays. Now let's learn to modify arrays in-place.

The splice() Method (Exercise 31)

splice() is a powerful method that can insert, remove, or replace elements:

const arr = ['a', 'b', 'c', 'd', 'e'];

// splice(startIndex, deleteCount)
arr.splice(2, 1);  // Remove 1 element at index 2
console.log(arr);  // ['a', 'b', 'd', 'e']

// splice(startIndex, deleteCount, ...newItems)
arr.splice(1, 2, 'X', 'Y');  // Remove 2, insert 'X', 'Y'
console.log(arr);  // ['a', 'X', 'Y', 'e']
Critical: Iterate BACKWARDS When Removing

When removing elements from an array while looping, go backwards.
Why? removing an element at i shifts all subsequent elements to the left (new i is the old i+1). A forward loop would increment i and skip an element!

// Exercise 31 Pattern
function filterRangeInPlace(arr, a, b) {
  // Loop BACKWARDS to avoid index shifting issues
  for (let i = arr.length - 1; i >= 0; i--) {
    if (arr[i] < a || arr[i] > b) {
      arr.splice(i, 1);  // Remove element at i
    }
  }
  // No return - we modified the original array
}

Building New Arrays (Exercise 32)

Sometimes you need to create a new array with unique values:

// Exercise 32 Pattern
function unique(arr) {
  const result = [];  // Accumulator
  
  for (let i = 0; i < arr.length; i++) {
    // Only add if not already in result
    if (!result.includes(arr[i])) {
      result.push(arr[i]);
    }
  }
  
  return result;
}
Mindset: Modify vs Return New (Immutability)

Exercise 31 "Modifies in-place". Good for memory efficiency.
Exercise 32 "Returns new array". This is Immutability. Modern frameworks like React prefer this because it makes tracking changes easier.

EXERCISE

Array Filtering

Write a function that removes all values outside the range [a, b] from an array in-place:

Your preview will appear here...

Quick Quiz: Why loop backwards when removing elements from an array?

A It's faster
B JavaScript requires it
C Removing shifts indices, so forward looping skips elements
D Arrays are stored backwards in memory

7.3 Object Transformations

A common pattern: take an array of objects and transform each into a different shape.

Transforming Objects (Exercise 33)

Given users with name and surname, create objects with fullName:

// Exercise 33 Pattern
function fullNames(arr) {
  const result = [];
  
  for (let i = 0; i < arr.length; i++) {
    // Create NEW object with different structure
    const newObj = {
      fullName: arr[i].name + " " + arr[i].surname,
      id: arr[i].id
    };
    result.push(newObj);
  }
  
  return result;
}

Grouping by ID (Exercise 34)

Convert an array of objects into an object where keys are the IDs:

// Input: [{id: "john", name: "John"}, {id: "ann", name: "Ann"}]
// Output: { john: {id: "john", name: "John"}, ann: {...} }

// Exercise 34 Pattern
function groupById(arr) {
  const result = {};  // Object, not array!
  
  for (let i = 0; i < arr.length; i++) {
    // Use the id as the KEY
    result[arr[i].id] = arr[i];
  }
  
  return result;
}
Key Insight: Dynamic Object Keys & Performance

Use obj[variable] to set a key using a variable's value.
Why do this? Searching for an item in an Array takes time (O(n)). Searching in an Object by key is instant (O(1)). This is a crucial optimization for large datasets!

Quick Quiz: To use a variable as an object key, which syntax is correct?

A obj[variableName]
B obj.variableName
C obj{variableName}
D obj->variableName

7.4 Constructor Functions & this

Sometimes you need to create multiple objects with the same methods and structure.

Why Constructor Functions?

Instead of manually writing `{...}` for every object, a Constructor is like a Blueprint or Factory.
Note: In modern JavaScript, we also use class syntax, which is syntactic sugar over this exact pattern.

Understanding the Problem (Exercise 35)

We need to create an "extensible calculator" that:

  • Parses strings like "3 + 7" and returns the result
  • Starts with + and - operations
  • Allows adding new operations later

Step-by-Step Logic Breakdown

Step 1: Store Operations as Functions

The key insight is: operators are just functions!
+ takes two numbers and returns their sum.
We can store these functions in an object to look them up dynamically.

// Operations stored as functions
const methods = {
  "+": (a, b) => a + b,  // Takes a, b, returns sum
  "-": (a, b) => a - b   // Takes a, b, returns difference
};

// To use: look up the operator, call the function
methods["+"](3, 7);  // Returns 10
methods["-"](10, 4); // Returns 6
Step 2: Parse the Input String

Given "3 + 7", we need to extract: first number, operator, second number.

const str = "3 + 7";

// Split by spaces into array
const parts = str.split(" ");  // ["3", "+", "7"]

const num1 = Number(parts[0]);     // 3 (convert string to number)
const operator = parts[1];         // "+"
const num2 = Number(parts[2]);     // 7
Step 3: Look Up and Execute

Use the operator to find the right function, then call it!

// Look up the function for this operator
const operatorFunction = methods[operator];  // methods["+"]

// Call it with our numbers
const result = operatorFunction(num1, num2); // 10

The Complete Solution

// Exercise 35 Pattern
function Calculator() {
  const obj = {
    // Store operators as functions
    methods: {
      "+": (a, b) => a + b,
      "-": (a, b) => a - b
    },
    
    calculate(str) {
      // Parse the string
      const parts = str.split(" ");
      const num1 = Number(parts[0]);
      const operator = parts[1];
      const num2 = Number(parts[2]);
      
      // Look up and execute
      if (!this.methods[operator] || isNaN(num1) || isNaN(num2)) {
          return NaN;
      }
      return this.methods[operator](num1, num2);
    },
    
    addMethod(name, func) {
      // Add new operation to methods object
      this.methods[name] = func;
    }
  };
  
  return obj;
}
Understanding this

Inside a method, this refers to the object calling the method.
this.methods looks for methods inside the current object.
Tip: this is dynamic! It's determined at the moment the function is NOT defined, but CALLED.

Using the Calculator

const calc = Calculator();  // Create calculator

console.log(calc.calculate("3 + 7"));  // 10
console.log(calc.calculate("10 - 4")); // 6

// Add new operations - this is the "extensible" part!
calc.addMethod("*", (a, b) => a * b);
calc.addMethod("**", (a, b) => a ** b);

console.log(calc.calculate("3 * 4"));  // 12
console.log(calc.calculate("2 ** 8")); // 256
Mindset: Data-Driven Design

Instead of hard-coding if (operator === "+") { return a + b; }, we store operations as data (functions in an object). This makes the code flexible - add new operations just by adding new data!

Quick Quiz: What does this refer to inside an object method?

A The window object
B The object that contains the method
C The function itself
D undefined

7.5 Closures & Function Chaining

This is one of JavaScript's most powerful (and tricky) concepts!

Why Closures Exist

Normally, variables inside a function are destroyed when the function ends. But what if you NEED to keep a value around? Closures let a function remember variables even after its parent function finishes.
Use Cases: This pattern is essential for Private Variables (encapsulation) and Factory Functions.

What is a Closure?

A closure is created when a function "captures" variables from its outer scope:

function createCounter() {
  let count = 0;  // This variable is "closed over"
  
  return function() {
    count++;  // Can still access count!
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
How It Works Step-by-Step

1. createCounter() runs, creates count = 0
2. It returns the inner function (which references count)
3. createCounter finishes, but count survives!
4. Every time you call counter(), it uses THE SAME count

Understanding the Challenge (Exercise 36)

We need to make sum(1)(2)(3) == 6 work. Let's break this down:

// Analyzing the Problem
// What does this syntax mean?
sum(1)(2)(3) == 6

// Let's break it into steps:
// Step 1: sum(1) is called → must return SOMETHING
// Step 2: that SOMETHING is called with (2)
// Step 3: that result is called with (3)
// Step 4: that result is compared with 6

// KEY INSIGHT: For (2) and (3) to work, 
// sum(1) must return a FUNCTION!
The Key Insight: Return a Function

If sum(1) needs to be called again like sum(1)(2), then sum(1) must return a function.

That returned function also returns itself, allowing infinite chaining!

Building the Solution Step-by-Step

// Step 1: Basic Structure
function sum(x) {
  let total = x;  // Remember the first number
  
  function add(y) {
    total += y;   // Add new number to total
    return add;   // Return OURSELVES for chaining
  }
  
  return add;  // Return the add function
}

// Now sum(1)(2)(3) works but...
// How do we get the final number out?
The valueOf Trick

When JavaScript compares a function with ==, it calls the function's valueOf() method to get a primitive value.

By adding add.valueOf = () => total, we tell JavaScript: "When you need a number, use total"

// Complete Solution (Exercise 36)
function sum(x) {
  let total = x;  // Closure: remember running total
  
  function add(y) {
    total += y;    // Add to the running total
    return add;    // Return itself for more chaining
  }
  
  // Magic: when compared with ==, JavaScript calls valueOf
  add.valueOf = () => total;
  
  return add;
}

// Let's trace through sum(1)(2)(3) == 6:
// 1. sum(1) → total=1, returns add function
// 2. add(2) → total=3, returns add function
// 3. add(3) → total=6, returns add function
// 4. add == 6 → calls valueOf() → returns 6
// 5. 6 == 6 → true!
Key Takeaways
  • Closures: Inner functions remember outer variables
  • Self-returning functions: Return yourself for chaining
  • Functions are objects: You can add properties like valueOf

Quick Quiz: What must sum(1) return for sum(1)(2) to work?

A A number
B An array
C An object
D A function

7.6 Promises & Async JavaScript

JavaScript often needs to wait for things (loading data, timers). Promises let you handle these "wait and then do something" situations.

Why Do We Need Async?

JavaScript runs on a single thread (it can only do one thing at a time).
The Event Loop queues up long tasks (like timers or network requests) to run later, so the main page doesn't freeze.
This is the foundation for modern `async/await` syntax!

Understanding setTimeout

Before promises, let's understand setTimeout:

console.log("1. Start");

setTimeout(() => {
  console.log("2. This runs LATER (after 2 seconds)");
}, 2000);

console.log("3. End");

// Output order: 1, 3, 2
// JavaScript doesn't WAIT - it schedules and moves on!
How setTimeout Works

setTimeout(callback, ms) says: "After ms milliseconds, run the callback function"

JavaScript sets the timer, then immediately continues to the next line!

The Problem: Callback Hell

What if you need to do things in sequence? With callbacks, it gets messy:

// The Problem (Nested Callbacks)
setTimeout(() => {
  console.log("Step 1");
  setTimeout(() => {
    console.log("Step 2");
    setTimeout(() => {
      console.log("Step 3");
      // This keeps indenting... "callback hell" 😱
    }, 1000);
  }, 1000);
}, 1000);

The Solution: Promises (Exercise 37)

Promises give us a cleaner way to handle async operations:

// Creating a Promise
function delay(ms) {
  // Create a new Promise
  return new Promise((resolve) => {
    // When timeout completes, call resolve()
    setTimeout(resolve, ms);
  });
}

// Usage:
delay(3000).then(() => {
  console.log("3 seconds passed!");
});
The Promise Contract

A Promise is an object that says "I might have a value later".
You call resolve() when you're done.
Who waits? The code using .then() waits!

Promise Chaining (Exercise 38)

The real magic is chaining steps flat, without nesting:

// Chained Promises
function showSequenceMessages() {
  delay(1000)
    .then(() => {
      console.log("Starting...");
      return delay(2000);  // ← Return next Promise!
    })
    .then(() => {
      console.log("Completed!");
      return delay(1000);
    })
    .then(() => {
      console.log("End of messages.");
    });
}
Execution Timeline

0ms: Start delay(1000)
1000ms: Log "Starting...", start delay(2000)
3000ms: Log "Completed!", start delay(1000)
4000ms: Log "End of messages."

Total time: 4 seconds, running in sequence!

Critical: You MUST Return the Promise

If you forget to return the next promise, they run in parallel, not sequence!

Wrong: .then(() => { delay(2000); }) ← missing return!
Right: .then(() => { return delay(2000); })

Key Takeaways
  • setTimeout schedules code to run later
  • Promises wrap async operations in a clean interface
  • .then() chains allow sequential async operations
  • Always return the next promise in a chain!

Quick Quiz: What do you call to signal a Promise is complete?

A complete()
B done()
C resolve()
D finish()
EXERCISE

Promise Delay

Write a delay(ms) function that returns a Promise that resolves after ms milliseconds:

Your preview will appear here...

7.7 JS + HTML + CSS Integration

Finally, let's connect JavaScript to the web page (DOM). We'll build a Live Form Validator.

The Goal (Exercise 39)

We want an input field that:
1. Listens for user typing
2. Checks if the value is valid (e.g., "contains @")
3. Changes color (Green/Red) instantly

The HTML Structure

<input type="text" id="username" placeholder="Enter username">
<div id="feedback"></div>

The CSS Classes

.valid { border-color: green; color: green; }
.invalid { border-color: red; color: red; }

The JavaScript Logic

// Exercise 39 Pattern
const input = document.getElementById('username');
const feedback = document.getElementById('feedback');

// Listen for "input" events (fires every keystroke)
input.addEventListener('input', function() {
  const value = input.value;
  
  if (value.includes('@')) {
    // Valid case
    input.className = 'valid';
    feedback.textContent = 'Valid username!';
  } else {
    // Invalid case
    input.className = 'invalid';
    feedback.textContent = 'Must contain @ symbol';
  }
});
Separation of Concerns

Notice how we just change the className?
We let CSS handle the colors and styles.
We let JavaScript handle the logic.
This keeps our code clean and maintainable!

Quick Quiz: Which method attaches an event handler to an element?

A element.onClick(handler)
B element.addEventListener('click', handler)
C element.attachEvent('click', handler)
D element.listen('click', handler)
EXERCISE

Form Validation

Write code that adds the class "valid" if the input contains "user", and "invalid" otherwise:

Your preview will appear here...

7.8 Exercise Mapping

Here is a map of the exercises we've covered in this chapter:

Topic Exercise ID Description
Array Methods #31, #32 Filter range in-place; Unique array members
Objects #33, #34 Map to objects; Group by ID
Constructors #35 Extensible Calculator
Closures #36 Function chaining sum(a)(b)
Promises #37, #38 Delay function; Promise chaining
DOM #39 Live form validation