DEV Community

loading...
Cover image for Understanding Array.map by Rolling Your Own

Understanding Array.map by Rolling Your Own

nas5w profile image Nick Scialli (he/him) ・3 min read

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
}

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;
}

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]

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;
}

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]

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);
    });
  });

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

Discussion (1)

Collapse
mellen profile image
Matt Ellen

TIL map has a thisArg!

Forem Open with the Forem app