Skip to the code
cf.

Array.prototype.includes

Write Array.prototype.myIncludes(searchElement, fromIndex), which works like the built-in includes without calling it. It returns true if an element of the array equals searchElement, and false if none does.

Elements are compared the way the spec's SameValueZero does: like ===, with no type conversion, except that NaN equals NaN. 0 and -0 are equal, as they are with ===. That's why [NaN].includes(NaN) is true, while [NaN].indexOf(NaN) is -1.

Every index is checked, holes included, and a hole reads as undefined. So [1, , 3].myIncludes(undefined) is true.

fromIndex is a whole number: the index where the search starts. It defaults to 0. A negative fromIndex counts back from the end, so -1 means the last index; if that's still below 0, the search starts at 0. A fromIndex at or past the length searches nothing and gives false.

[1, 2, 3].myIncludes(2);        // true
["1", 2].myIncludes(1);         // false: no type conversion
[NaN].myIncludes(NaN);          // true
[-0].myIncludes(0);             // true
[1, , 3].myIncludes(undefined); // true: the hole reads as undefined
[1, 2, 3].myIncludes(1, 1);     // false: starts at index 1
[1, 2, 3].myIncludes(3, -1);    // true: starts at index 2
[1, 2, 3].myIncludes(1, -10);   // true: -10 + 3 is below 0, so it starts at 0

What the tests check

  1. returns whether the array holds the value
    expect([1, 2, 3].myIncludes(2)).toBe(true);
    expect(["a", "b"].myIncludes("b")).toBe(true);
    expect([1, 2, 3].myIncludes(4)).toBe(false);
    expect([].myIncludes(undefined)).toBe(false);
    
  2. compares without converting types
    expect(["1", 2].myIncludes(1)).toBe(false);
    expect([0, ""].myIncludes(false)).toBe(false);
    expect([null].myIncludes(undefined)).toBe(false);
    expect([true].myIncludes(1)).toBe(false);
    
  3. finds objects by identity, not by their contents
    const item = { id: 1 };
    expect([{ id: 0 }, item].myIncludes(item)).toBe(true);
    expect([{ id: 1 }].myIncludes({ id: 1 })).toBe(false);
    
  4. finds NaN
    expect([1, NaN].myIncludes(NaN)).toBe(true);
    expect([1, 2].myIncludes(NaN)).toBe(false);
    expect(["NaN", "text", undefined].myIncludes(NaN)).toBe(false);
    
  5. treats 0 and -0 as equal
    expect([-0].myIncludes(0)).toBe(true);
    expect([1, 0].myIncludes(-0)).toBe(true);
    
  6. reads holes as undefined
    expect([1, , 3].myIncludes(undefined)).toBe(true);
    expect(new Array(3).myIncludes(undefined)).toBe(true);
    expect([1, , 3].myIncludes(null)).toBe(false);
    
  7. starts the search at fromIndex
    expect([1, 2, 3].myIncludes(1, 1)).toBe(false);
    expect([1, 2, 3].myIncludes(2, 1)).toBe(true);
    expect([1, 2, 3].myIncludes(1, 0)).toBe(true);
    expect([1, 2, 3].myIncludes(3, 3)).toBe(false);
    expect([1, 2, 3].myIncludes(3, 10)).toBe(false);
    
  8. counts a negative fromIndex back from the end, stopping at 0
    expect([1, 2, 3].myIncludes(3, -1)).toBe(true);
    expect([1, 2, 3].myIncludes(2, -1)).toBe(false);
    expect([1, 2, 3].myIncludes(1, -3)).toBe(true);
    expect([1, 2, 3].myIncludes(1, -10)).toBe(true);
    // Starts at index 0: there's no index -7 to read.
    expect([1, 2, 3].myIncludes(undefined, -10)).toBe(false);
    
  9. works without the built-in includes
    const builtIn = Array.prototype.includes;
    Array.prototype.includes = function () {
      throw new Error("myIncludes called the built-in includes");
    };
    let results;
    try {
      results = [[1, NaN].myIncludes(NaN), [1, 2].myIncludes(3)];
    } finally {
      Array.prototype.includes = builtIn;
    }
    expect(results).toEqual([true, false]);
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).