Reimplementing Array.prototype.map
JavaScript's Array.prototype.map() is a fundamental method for transforming arrays. It creates a new array populated with the results of calling a provided function on every element in the calling array. This challenge asks you to recreate this functionality from scratch, enhancing your understanding of array manipulation and higher-order functions.
Problem Description
Your task is to implement a function named myMap that mimics the behavior of Array.prototype.map(). This function will be added to the Array.prototype so it can be called directly on any array instance.
Requirements:
- The
myMapfunction should accept two arguments:callback: A function to execute for each element in the array.thisArg(optional): A value to use asthiswhen executing thecallbackfunction.
- The
callbackfunction will be called with three arguments:currentValue: The current element being processed in the array.index: The index of thecurrentValuein the array.array: The arraymyMapwas called upon.
myMapshould return a new array. It should not modify the original array.- The
myMapfunction should iterate over all elements of the array, even if some elements areundefinedor have been deleted (sparse arrays). - If
thisArgis provided, it should be used as thethiscontext for thecallbackfunction. - If the
callbackfunction is not provided or is not a function, aTypeErrorshould be thrown.
Expected Behavior:
- For each element in the original array,
myMapwill call thecallbackfunction. - The return value of each
callbackinvocation will be placed into the corresponding position in the new array. - If an element in the original array is
undefined, thecallbackshould still be invoked for that element. - If the array is empty,
myMapshould return an empty array.
Examples
Example 1:
const numbers = [1, 2, 3, 4];
const doubledNumbers = numbers.myMap(function(num) {
return num * 2;
});
// doubledNumbers is [2, 4, 6, 8]
// numbers is still [1, 2, 3, 4]
Explanation: The callback function (num) => num * 2 is applied to each element of the numbers array. The results are collected into a new array doubledNumbers.
Example 2:
const words = ["hello", "world"];
const lengths = words.myMap((word, index) => `${index}: ${word.length}`);
// lengths is ["0: 5", "1: 5"]
Explanation: The callback uses both the word and its index to create a new string for each element.
Example 3:
const sparseArray = [1, , 3];
const mappedSparse = sparseArray.myMap(function(element, index) {
console.log(`Processing index ${index} with value: ${element}`);
return element === undefined ? 'empty' : element * 10;
});
// Output to console:
// Processing index 0 with value: 1
// Processing index 2 with value: 3
// mappedSparse is [10, undefined, 30] (Note: the empty slot results in undefined for callback, and the new array preserves the slot's nature conceptually)
Explanation: Even though there's an empty slot (which translates to undefined when accessed), myMap iterates over it and calls the callback. The callback's return value determines what goes into the new array. For the empty slot, the callback receives undefined and returns 'empty' in this specific example's callback, but the new array at that index would technically be empty/undefined if the callback returned undefined. This example shows that the iteration occurs. A more accurate representation for a truly sparse result would be if the callback explicitly returned undefined for the empty slot. Let's adjust the example for clarity on sparse output.
Example 3 (Revised for Sparse Output):
const sparseArray = [1, , 3];
const mappedSparse = sparseArray.myMap(function(element, index) {
// The callback will be called for index 0, index 1, and index 2
// 'element' will be undefined for index 1
return element !== undefined ? element * 10 : undefined;
});
// mappedSparse is [10, undefined, 30]
Explanation: The callback is invoked for all indices. For the empty slot (index 1), element is undefined. The callback checks for undefined and returns undefined for that position in the new array.
Example 4 (with thisArg):
const multiplier = 5;
const numbers = [1, 2, 3];
const multiplied = numbers.myMap(function(num) {
return num * this.multiplier;
}, { multiplier: multiplier });
// multiplied is [5, 10, 15]
Explanation: The thisArg object is used as the this context within the callback function, allowing access to its multiplier property.
Constraints
- The implementation should not use the built-in
Array.prototype.map. - The implementation should handle arrays of any size, from empty to very large.
- The
callbackfunction should be invoked with the correctthisArg. - The
myMapfunction should be added directly toArray.prototype.
Notes
- Remember to handle the case where
callbackis not a function. - Consider how you would iterate over sparse arrays to ensure every potential index is accounted for, even if it doesn't hold a value.
- The
thisArgis optional. If it's not provided,thisinside the callback will beundefinedin strict mode, or the global object in non-strict mode, consistent withArray.prototype.map. - Think about the return value of the
callback. It can be any data type. - The new array should have the same length as the original array.