DEV Community

Cover image for Understanding Array.map by Rolling Your Own
Nick Scialli (he/him)
Nick Scialli (he/him)

Posted on

Understanding Array.map by Rolling Your Own

One way to understand JavaScript methods is to roll your own version. Today, let's write Array.map!

It turns out, Array.map takes two arguments:

  • The function that will be applied as you loop over the array
  • A thisArg, which will be a reference to an object that will be the this context in the provided function.

In my experience, I have not really used the second argument, but we'll want to be sure to include it.

Write the Interface

Since I'm not interested in extending the Array prototype, I'll just create a separate map function. Therefore, I'll actually pass the array as an argument, meaning we'll have three total arguments:

function map(arr, fn, thisArg) {
  // Magic goes here
}
Enter fullscreen mode Exit fullscreen mode

Applying the Function to Each Element

The fn we provide must be applied to each element of the array. Let's make that happen.

function map(arr, fn, thisArg) {
  const len = arr.length;
  const result = new Array(len);
  for (let i = 0; i < len; i++) {
    if (i in arr) {
      result[i] = fn(arr[i], i, arr);
    }
  }
  return result;
}
Enter fullscreen mode Exit fullscreen mode

Importantly, we pass three arguments to fn: The current array element, the index of the current array element, and the original input array. Let's see this in action:

const mapped = map([1, 2, 3], el => el * 2);

console.log(mapped);
// [2, 4, 6]
Enter fullscreen mode Exit fullscreen mode

Great, looks like the basics are working! This example doesn't include use of the i or arr passed to our fn, but you can test that on your own.

Finally, the thisArg

Let's not forget the thisArg! If thisArg is provided, we want to make sure we bind our provided function to the thisArg. Here's the amended code to make it work:

function map(arr, fn, thisArg) {
  fn = thisArg === undefined ? fn : fn.bind(thisArg);
  const len = arr.length;
  const result = new Array(len);
  for (let i = 0; i < len; i++) {
    if (i in arr) {
      result[i] = fn(arr[i], i, arr);
    }
  }
  return result;
}
Enter fullscreen mode Exit fullscreen mode

And here it is in action. (Note that my provided function can't be an arrow function since you cannot rebind an arrow function's this reference.)

const obj = {
  num: 10,
};

const mapped = map(
  [1, 2, 3],
  function (el) {
    return el + this.num;
  },
  obj
);

console.log(mapped);
// [11, 12, 13]
Enter fullscreen mode Exit fullscreen mode

And we can see this refers to obj!

Bonus: Write Your Own with Test-Driven Development

I wrote this map function using Test-Driven Development (TDD)! I laid out tests for all the scenarios that needed to pass for Array.map and then, one-by-one, reworked the code to make them pass. Consider using this method if you try this with other built-in JS methods.

Here are the tests I used for the map function:

describe("array", () => {
  describe("map", () => {
    it("maps a simple array", () => {
      const arr = [1, 2, 3];
      const fn = el => el * 10;
      const answer = arr.map(fn);
      expect(map(arr, fn)).toEqual(answer);
    });
    it("maps an empty array", () => {
      const arr = [];
      const fn = el => el * 10;
      const answer = arr.map(fn);
      expect(map(arr, fn)).toEqual(answer);
    });
    it("maps an array with holes", () => {
      const arr = [1, 2, , , 3];
      const fn = el => el * 10;
      const answer = arr.map(fn);
      expect(map(arr, fn)).toEqual(answer);
    });
    it("uses thisArg", () => {
      const obj = {
        0: "foo",
        1: "bar",
        2: "baz"
      };
      const arr = [1, 2, 3];
      const fn = function(el, i) {
        return this[i] + el;
      };
      const answer = arr.map(fn, obj);
      expect(map(arr, fn, obj)).toEqual(answer);
    });
    it("uses the idx and arr parameters", () => {
      const arr = [1, 2, 3];
      const fn = (el, idx, arr) => JSON.stringify([el, idx, arr]);
      const answer = arr.map(fn);
      expect(map(arr, fn)).toEqual(answer);
    });
  });
Enter fullscreen mode Exit fullscreen mode

I hope you enjoyed this! Let me know if you end up rolling your own versions of other built-in methods.

Top comments (1)

Collapse
 
mellen profile image
Matt Ellen

TIL map has a thisArg!