JS10 Exercises
Practical
- Implementing Custom Iterator
Objective: Create a custom iterator for a specific range of numbers.
Tasks:
Design a rangeIterator function that takes two arguments: start and end. The iterator should yield numbers from start to end (inclusive) when its next() method is called. Ensure the iterator respects the iterator protocol and properly terminates after reaching end.
function rangeIterator(start, end) {
let nextIndex = start;
return {
next: function() {
if (nextIndex <= end) {
return { value: nextIndex++, done: false };
} else {
return { done: true };
}
}
};
}
// Example usage
const it = rangeIterator(1, 5);
console.log(it.next().value); // 1
console.log(it.next().value); // 2
console.log(it.next().value); // 3
console.log(it.next().value); // 4
console.log(it.next().value); // 5
console.log(it.next().done); // true- Creating an Iterable Object
Objective: Implement the iterable protocol on a custom object.
Tasks:
Create an object myIterable with properties and methods. Implement the Symbol.iterator method so that the object becomes iterable. The iterator should iterate over a predefined set of values in the object. Test the iterable using a for...of loop.
const myIterable = {
data: [1, 2, 3, 4, 5],
[Symbol.iterator]: function() {
let index = 0;
let data = this.data;
return {
next: function() {
return index < data.length ?
{ value: data[index++], done: false } :
{ done: true };
}
};
}
};
// Test
for (const value of myIterable) {
console.log(value); // 1, 2, 3, 4, 5
}- Understanding Generator Functions
Objective: Write a generator function to understand the yield keyword.
Tasks:
Write a generator function numberGenerator that generates numbers up to a specified limit. The function should use the yield keyword to return the next number in each iteration. Test the generator by iterating over its returned generator object with a for...of loop.
function* numberGenerator(limit) {
for (let i = 0; i <= limit; i++) {
yield i;
}
}
// Test
for (const num of numberGenerator(5)) {
console.log(num); // 0, 1, 2, 3, 4, 5
}- Combining Iterators and Generators
Objective: Create a generator that delegates to another generator.
Tasks:
Write a generator function multiplierGenerator that multiplies each number from an input sequence by a specified factor. Use the yield* syntax to delegate to another generator. Test the combined functionality with an array of numbers.
function* baseGenerator() {
yield* [1, 2, 3, 4, 5];
}
function* multiplierGenerator(factor) {
for (const value of baseGenerator()) {
yield value * factor;
}
}
// Test
for (const num of multiplierGenerator(2)) {
console.log(num); // 2, 4, 6, 8, 10
}- Implementing an Infinite Iterator
Objective: Create an iterator that generates an infinite sequence of numbers.
Tasks:
Design an iterator infiniteNumbers that continuously yields numbers starting from a given value. The next() method should never signal that the sequence is complete. Test by iterating over the sequence but implement a break condition to avoid an infinite loop in practice.
function infiniteNumbers(start = 0) {
let number = start;
return {
next: function() {
return { value: number++, done: false };
}
};
}
// Example usage (with break condition)
const it = infiniteNumbers(1);
for (let i = 0; i < 10; i++) {
console.log(it.next().value); // 1, 2, ..., 10
}- Utilizing Generators with for...of Loop
Objective: Write a generator function and utilize it with the for...of loop to iterate over a complex data structure.
Tasks:
Create a complex data structure, like a nested array or a tree-like object. Write a generator function that can iterate over this structure, yielding values in a specific order (e.g., depth-first for a tree). Use a for...of loop to iterate over the data structure and print each value.
const nestedArray = [1, [2, 3], [4, [5, 6]]];
function* flattenArray(arr) {
for (const item of arr) {
if (Array.isArray(item)) {
yield* flattenArray(item);
} else {
yield item;
}
}
}
// Test
for (const value of flattenArray(nestedArray)) {
console.log(value); // 1, 2, 3, 4, 5, 6
}