loading...
Cover image for Let's re-implement JavaScript's Array.map method!

Let's re-implement JavaScript's Array.map method!

jchiatt profile image J.C. Hiatt ・3 min read

I've been posting daily JavaScript challenges on Twitter lately, and today's challenge involves re-implementing Array.map.

This is certainly not the only solution for this, but here's my stab at it.

What does Array.map do?

Full details can be found on the MDN docs, but here's the gist of things:

  1. .map lives on the Array prototype
  2. It returns a new Array from after calling a callback function on every element of the array on which you call it.
  3. The callback function has 3 arguments, the last two of which are optional: the current item, the current index, and the original array you called .map on.
  4. You can optionally pass in a second argument after the callback, which will be the this context to call the callback with. If you don't pass in a this context, the callback will be called with undefined as the this context.

Example of Array.map

const exercises = [
  "Bench Press", 
  "Svend Press", 
  "Chest Flyes",
  "Kettlebell Swing",
  "40 Yard Sprint",
];

const arr = exercises.map(item => {
  return "mapped " + item";
})

// returns:
[ 'mapped Bench Press',
  'mapped Svend Press',
  'mapped Chest Flyes',
  'mapped Kettlebell Swing',
  'mapped 40 Yard Sprint' ]

Re-implementing the method

1. Set up our function with the arguments we know it will take.

We know map accepts a callback and a this context. We also know that the this context will be set to undefined if one is not passed in, so we'll set a default value on that argument.

function newMap(callback, thisArg = undefined) {
  // more code will go here
}

2. Exit early if there's a problem and give error messages.

First check is to make sure newMap was called on an array.

function newMap(callback, thisArg = undefined) {
  // Ensure it called on an array.
  if (!Array.isArray(this)) {
    throw new TypeError('Must be called on an array');
  }
}

Next, make sure a callback was supplied since it is required.

function newMap(callback, thisArg = undefined) {
  // Ensure it called on an array.
  if (!Array.isArray(this)) {
    throw new TypeError('Must be called on an array');
  }

  // Make sure a callback was supplied.
  if ( !callback ) {
    throw new TypeError('undefined is not a function');
  }
}

Lastly, make sure that if a callback was supplied, it is a function.

function newMap(callback, thisArg = undefined) {
  // Ensure it called on an array.
  if (!Array.isArray(this)) {
    throw new TypeError('Must be called on an array');
  }

  // Ensure a callback was supplied.
  if ( !callback ) {
    throw new TypeError('undefined is not a function');
  }

  // Ensure the supplied callback is a function.
  if ( typeof callback !== 'function' ) {
    throw new TypeError(callback + " is not a function");
  }
}

3. Execute the callback for each item in the array.

function newMap(callback, thisArg = undefined) {
  // Ensure it called on an array.
  if (!Array.isArray(this)) {
    throw new TypeError('Must be called on an array');
  }

  // Ensure a callback was supplied.
  if ( !callback ) {
    throw new TypeError('undefined is not a function');
  }

  // Ensure the supplied callback is a function.
  if ( typeof callback !== 'function' ) {
    throw new TypeError(callback + " is not a function");
  }

  /**
  * Make a copy of the original array, just in case the callback 
  * does any mutations on the current value.
  */
  const self = Array.from(this);

  // Initialize a new array to build and return.
  let newArray = [];

  // Loop through the length of the original array.
  for (i = 0; i < this.length; i++) {
    /**
    * Execute the callback with `thisArg` as the `this` context.
    * Callback is executed with 3 arguments:
    * self[i] is the current value
    * i is the current index
    * this is the original array
    */
    let result = callback.call(thisArg, self[i], i, this);

    // Add the result of the callback to the new array.
    newArray[i] = result;
  }

  // Return the new array.
  return newArray;
}

Testing our new method.

We can attach our new method to the Array prototype and test it out like this:

Array.prototype.newMap = newMap;

const arr = exercises.newMap(item => {
  return {% raw %}`new ${item}`{% endraw %};
})

// Returns the same result as Array.map
[ 'new Bench Press',
  'new Svend Press',
  'new Chest Flyes',
  'new Kettlebell Swing',
  'new 40 Yard Sprint' ]

Interested in more tips and challenges? Follow me on Twitter and also check out my collection of previous tips and challenges.

Posted on Mar 18 '19 by:

jchiatt profile

J.C. Hiatt

@jchiatt

Full-stack engineer. Egghead instructor. MagnoliaJS & JacksonDevs organizer. I follow Jesus. Reformed. Libertarian. BJJ. Powerlifting. I ❤️ JS. Side hustle: DevLifts.io

Discussion

markdown guide
 

Nice article, and I'm actually about to make a video on this exact same subject!

I noticed that when you do the for loop, there's i = 0, instead of let i = 0, and this could result in the creation (or mutation) of a global variable. Funny enough, i is a global variable on dev.to at the moment, and it's value is 0! But once you run the code above, it gets updated.

Good job on this article!