Advanced JavaScript Exercises (Part 3)
Lesson Objective:
The goal of this lesson is to further challenge learners by focusing solely on JavaScript without relying on any external libraries or frameworks. These exercises dive deeper into fundamental concepts like DOM manipulation, event handling, custom data structures, advanced functions, and more.
Learning Outcomes:
- Implement custom solutions without libraries, reinforcing core JavaScript knowledge.
- Manipulate the DOM programmatically.
- Develop complex features using only JavaScript.
- Gain a better understanding of ES6+ features in real-world scenarios.
Exercise 1: Custom Deep Clone Function
Learning Objective:
Understand how to create a custom deep clone function to copy objects with nested properties.
function deepClone(obj) {
if (obj === null || typeof obj !== ‘object’) {
return obj;
}
const copy = Array.isArray(obj) ? [] : {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepClone(obj[key]);
}
}
return copy;
}
const original = { name: ‘Alice’, details: { age: 25, city: ‘New York’ } };
const clone = deepClone(original);
clone.details.age = 30;
console.log(original.details.age); // 25
console.log(clone.details.age); // 30
Explanation:
- This deep clone function recursively copies all nested properties, ensuring that any changes to the cloned object do not affect the original object.
- This is essential for handling deeply nested objects in JavaScript.
Exercise 2: Implement a Custom Event Listener
Learning Objective:
Learn how to create a basic custom event listener system to handle custom events.
class EventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
}
emit(event, …args) {
if (this.events[event]) {
this.events[event].forEach(listener => listener(…args));
}
}
off(event, listenerToRemove) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(listener => listener !== listenerToRemove);
}
}
}
const emitter = new EventEmitter();
const greet = (name) => console.log(`Hello, ${name}!`);
emitter.on(‘greet’, greet);
emitter.emit(‘greet’, ‘Alice’); // “Hello, Alice!”
emitter.off(‘greet’, greet);
emitter.emit(‘greet’, ‘Bob’); // No output
Explanation:
- This exercise implements a custom event system with on, emit, and off methods, mimicking how event-driven architectures work in JavaScript.
- You can register listeners, trigger events, and remove listeners programmatically.
Exercise 3: Throttling Function
Learning Objective:
Implement a function that limits how frequently a given function can be called (throttling).
function throttle(func, delay) {
let lastCall = 0;
return function(…args) {
const now = new Date().getTime();
if (now – lastCall >= delay) {
lastCall = now;
return func(…args);
}
};
}
window.addEventListener(‘resize’, throttle(() => {
console.log(‘Resized’);
}, 2000));
Explanation:
- Throttling ensures that a function is called at most once every specified delay period (e.g., on window resize).
- The throttling function prevents performance issues by limiting how often the event handler is executed.
Exercise 4: DOM Manipulation – To-Do List
Learning Objective:
Create a simple to-do list that adds, deletes, and toggles tasks using JavaScript.
<ul id=”todoList”></ul>
<input id=”newTask” type=”text” placeholder=”Add a new task”>
<button id=”addTaskBtn”>Add Task</button>
<script>
const todoList = document.getElementById(‘todoList’);
const newTaskInput = document.getElementById(‘newTask’);
const addTaskBtn = document.getElementById(‘addTaskBtn’);
addTaskBtn.addEventListener(‘click’, () => {
const taskText = newTaskInput.value.trim();
if (taskText === ”) return;
const listItem = document.createElement(‘li’);
listItem.textContent = taskText;
listItem.addEventListener(‘click’, () => {
listItem.classList.toggle(‘completed’);
});
const deleteBtn = document.createElement(‘button’);
deleteBtn.textContent = ‘Delete’;
deleteBtn.addEventListener(‘click’, () => {
todoList.removeChild(listItem);
});
listItem.appendChild(deleteBtn);
todoList.appendChild(listItem);
newTaskInput.value = ”;
});
</script>
<style>
.completed {
text-decoration: line-through;
}
</style>
Explanation:
- This exercise demonstrates DOM manipulation by dynamically adding, deleting, and toggling tasks in a to-do list using JavaScript.
- The classList.toggle method is used to add or remove the completed class, marking a task as complete.
Exercise 5: Custom Promise Implementation
Learning Objective:
Implement a simple version of a JavaScript Promise for understanding its core concepts.
class SimplePromise {
constructor(executor) {
this.onResolve = null;
this.onReject = null;
executor(this.resolve.bind(this), this.reject.bind(this));
}
then(onResolve) {
this.onResolve = onResolve;
return this;
}
catch(onReject) {
this.onReject = onReject;
return this;
}
resolve(value) {
if (this.onResolve) {
this.onResolve(value);
}
}
reject(error) {
if (this.onReject) {
this.onReject(error);
}
}
}
const promise = new SimplePromise((resolve, reject) => {
setTimeout(() => resolve(‘Promise Resolved’), 1000);
});
promise.then(result => console.log(result)); // “Promise Resolved” after 1 second
Explanation:
- This basic implementation of a Promise replicates core behaviors like then and catch.
- Learners can see how asynchronous operations are handled, and how the resolve and reject mechanisms work under the hood.
Exercise 6: Custom Set Data Structure
Learning Objective:
Learn how to create a custom Set data structure that mimics the built-in JavaScript Set.
class CustomSet {
constructor() {
this.items = {};
}
add(value) {
if (!this.has(value)) {
this.items[value] = true;
}
}
delete(value) {
if (this.has(value)) {
delete this.items[value];
}
}
has(value) {
return this.items.hasOwnProperty(value);
}
size() {
return Object.keys(this.items).length;
}
clear() {
this.items = {};
}
}
const set = new CustomSet();
set.add(1);
set.add(2);
console.log(set.has(1)); // true
set.delete(1);
console.log(set.size()); // 1
Explanation:
- This custom Set class implements essential methods like add, delete, and has.
- The Set ensures that duplicate values aren’t stored, mimicking JavaScript’s built-in Set.
Exercise 7: Array Flattening Function
Learning Objective:
Implement a function to flatten deeply nested arrays.
function flattenArray(arr) {
return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenArray(val)) : acc.concat(val), []);
}
const nestedArray = [1, [2, [3, [4]], 5]];
const flatArray = flattenArray(nestedArray);
console.log(flatArray); // [1, 2, 3, 4, 5]
Explanation:
- This recursive function flattens any deeply nested array structure into a single-level array.
- Recursion is used to check if an element is an array and continues flattening until all nested arrays are resolved.
Exercise 8: Object Property Watcher
Learning Objective:
Create a function that watches for changes in an object’s property values and executes a callback when they change.
function watchObject(obj, prop, callback) {
let value = obj[prop];
Object.defineProperty(obj, prop, {
get() {
return value;
},
set(newValue) {
value = newValue;
callback(newValue);
}
});
}
const person = { name: ‘Alice’ };
watchObject(person, ‘name’, (newValue) => {
console.log(`Name changed to: ${newValue}`);
});
person.name = ‘Bob’; // “Name changed to: Bob”
Explanation:
- This exercise uses Object.defineProperty to watch for changes in an object’s property and execute a callback function when the property value changes.
- This is a basic approach to reactive programming without frameworks.
Exercise 9: Simple Countdown Timer
Learning Objective:
Learn how to create a countdown timer using JavaScript and DOM manipulation.
<p id=”timerDisplay”>10</p>
<button id=”startTimer”>Start Timer</button>
<script>
const timerDisplay = document.getElementById(‘timerDisplay’);
const startButton = document.getElementById(‘startTimer’);
let countdownValue = 10;
startButton.addEventListener(‘click’, () => {
const intervalId = setInterval(() => {
countdownValue–;
timerDisplay.textContent = countdownValue;
if (countdownValue === 0) {
clearInterval(intervalId);
alert(‘Time is up!’);
}
}, 1000);
});
</script>
Explanation:
- This exercise creates a countdown timer that starts from 10 and decrements by 1 every second.
- When the countdown reaches 0, the timer stops and triggers an alert.
- It demonstrates interval-based execution using setInterval() and DOM updates with textContent.
Exercise 10: Custom Sorting Function
Learning Objective:
Understand how to implement a custom sorting algorithm (bubble sort) using JavaScript.
function bubbleSort(arr) {
let swapped;
do {
swapped = false;
for (let i = 0; i < arr.length – 1; i++) {
if (arr[i] > arr[i + 1]) {
[arr[i], arr[i + 1]] = [arr[i + 1], arr[i]]; // Swap
swapped = true;
}
}
} while (swapped);
return arr;
}
const unsortedArray = [5, 3, 8, 4, 2];
const sortedArray = bubbleSort(unsortedArray);
console.log(sortedArray); // [2, 3, 4, 5, 8]
Explanation:
- Bubble sort is a simple sorting algorithm that repeatedly steps through the list, compares adjacent elements, and swaps them if they are in the wrong order.
- This exercise implements bubble sort using JavaScript, allowing learners to understand the inner workings of sorting algorithms.
Exercise 11: Image Carousel (Manual)
Learning Objective:
Create a manual image carousel that changes images on button click.
<div>
<img id=”carouselImage” src=”image1.jpg” alt=”carousel” width=”400″>
</div>
<button id=”prevBtn”>Previous</button>
<button id=”nextBtn”>Next</button>
<script>
const images = [‘image1.jpg’, ‘image2.jpg’, ‘image3.jpg’];
let currentIndex = 0;
const carouselImage = document.getElementById(‘carouselImage’);
const prevBtn = document.getElementById(‘prevBtn’);
const nextBtn = document.getElementById(‘nextBtn’);
prevBtn.addEventListener(‘click’, () => {
currentIndex = (currentIndex – 1 + images.length) % images.length;
carouselImage.src = images[currentIndex];
});
nextBtn.addEventListener(‘click’, () => {
currentIndex = (currentIndex + 1) % images.length;
carouselImage.src = images[currentIndex];
});
</script>
Explanation:
- This manual image carousel allows users to cycle through images by clicking the “Previous” and “Next” buttons.
- It demonstrates the use of event listeners and dynamic DOM manipulation to update the image source.
Exercise 12: Random Color Generator
Learning Objective:
Create a function that generates a random color and applies it to the background of the webpage.
<button id=”changeColorBtn”>Change Background Color</button>
<script>
function getRandomColor() {
const letters = ‘0123456789ABCDEF’;
let color = ‘#’;
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
document.getElementById(‘changeColorBtn’).addEventListener(‘click’, () => {
document.body.style.backgroundColor = getRandomColor();
});
</script>
Explanation:
- The getRandomColor function generates a random hexadecimal color.
- This exercise showcases the use of random number generation and CSS style manipulation to dynamically change the background color of the page.
Exercise 13: Form Validation
Learning Objective:
Create a simple form validation function that checks for required fields before submission.
<form id=”myForm”>
<input type=”text” id=”name” placeholder=”Name”>
<input type=”email” id=”email” placeholder=”Email”>
<button type=”submit”>Submit</button>
</form>
<script>
document.getElementById(‘myForm’).addEventListener(‘submit’, function(event) {
const name = document.getElementById(‘name’).value.trim();
const email = document.getElementById(’email’).value.trim();
if (name === ” || email === ”) {
event.preventDefault();
alert(‘Both fields are required!’);
}
});
</script>
Explanation:
- This form validation script checks if both the name and email fields are filled before submitting the form.
- Event.preventDefault() is used to stop form submission if validation fails, and an alert informs the user about missing fields.
Exercise 14: Intersection Observer (Lazy Loading Images)
Learning Objective:
Use the Intersection Observer API to implement lazy loading for images.
<img class=”lazy” data-src=”image1.jpg” alt=”lazy image” width=”400″>
<img class=”lazy” data-src=”image2.jpg” alt=”lazy image” width=”400″>
<script>
const lazyImages = document.querySelectorAll(‘.lazy’);
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.getAttribute(‘data-src’);
observer.unobserve(img);
}
});
});
lazyImages.forEach(image => {
imageObserver.observe(image);
});
</script>
Explanation:
- This exercise demonstrates lazy loading images using the Intersection Observer API.
- Images are only loaded when they come into view, improving page load performance.
Exercise 15: Simple Drag-and-Drop
Learning Objective:
Implement basic drag-and-drop functionality with JavaScript.
<div id=”draggable” style=”width: 100px; height: 100px; background: red; position: absolute;”></div>
<script>
const draggable = document.getElementById(‘draggable’);
let offsetX, offsetY;
draggable.addEventListener(‘mousedown’, (event) => {
offsetX = event.clientX – draggable.offsetLeft;
offsetY = event.clientY – draggable.offsetTop;
document.addEventListener(‘mousemove’, moveElement);
});
document.addEventListener(‘mouseup’, () => {
document.removeEventListener(‘mousemove’, moveElement);
});
function moveElement(event) {
draggable.style.left = `${event.clientX – offsetX}px`;
draggable.style.top = `${event.clientY – offsetY}px`;
}
</script>
Explanation:
- This drag-and-drop functionality allows the user to click and drag a red square around the page.
- Event listeners for mousedown, mousemove, and mouseup are used to track the element’s position and move it accordingly.
Exercise 16: Input Character Counter
Learning Objective:
Create a live character counter for an input field.
<textarea id=”textInput” rows=”4″ cols=”50″></textarea>
<p>Characters remaining: <span id=”charCount”>100</span></p>
<script>
const textInput = document.getElementById(‘textInput’);
const charCountDisplay = document.getElementById(‘charCount’);
const maxLength = 100;
textInput.addEventListener(‘input’, () => {
const remaining = maxLength – textInput.value.length;
charCountDisplay.textContent = remaining >= 0 ? remaining : 0;
});
</script>
Explanation:
- This exercise implements a character counter that updates in real time as the user types into the textarea.
- It helps users keep track of their input length and ensures that they stay within the maximum character limit.
These additional exercises build on key JavaScript concepts, such as DOM manipulation, event handling, asynchronous processing, and user interaction, all using JavaScript. They provide a foundation for learners to develop more complex applications without relying on external libraries or frameworks.