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.
- Problem-solving mindset and debugging strategies
- Advanced array methods: splice, includes, building new arrays
- Object transformations and grouping patterns
- Constructor functions and the
thiskeyword - 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:
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.
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.
Translate your plan into JavaScript. Start with small, testable pieces rather than the whole function at once.
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?
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']
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;
}
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.
Array Filtering
Write a function that removes all values outside the range [a, b] from an array in-place:
Quick Quiz: Why loop backwards when removing elements from an array?
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;
}
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?
7.4 Constructor Functions & this
Sometimes you need to create multiple objects with the same methods and structure.
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
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
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
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;
}
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
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?
7.5 Closures & Function Chaining
This is one of JavaScript's most powerful (and tricky) concepts!
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
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!
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?
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!
- 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?
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.
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!
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!");
});
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.");
});
}
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!
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); })
setTimeoutschedules code to run later- Promises wrap async operations in a clean interface
.then()chains allow sequential async operations- Always
returnthe next promise in a chain!
Quick Quiz: What do you call to signal a Promise is complete?
Promise Delay
Write a delay(ms) function that returns a Promise that resolves after
ms milliseconds:
7.7 JS + HTML + CSS Integration
Finally, let's connect JavaScript to the web page (DOM). We'll build a Live Form Validator.
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';
}
});
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?
Form Validation
Write code that adds the class "valid" if the input contains "user", and
"invalid" otherwise:
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 |