Array Out of Bounds: Comparison with undefined, or length check?

Viewed 61371

this seems to be a common javascript idiom:

function foo (array, index) {
    if (typeof array[index] == 'undefined')
        alert ('out of bounds baby');
}

as opposed to the more prevalent (in other languages) and conceptually simpler:

function foo (array, index) {
    if (index >= array.length)
        alert ('boo');
}

I understand that the first case will also work for arrays which have 'gaps' in them, but is that a common enough case to warrant the idiom?

The code sample that prompted this question can be seen here. In this case, when using the 'argument' variable inside a function, isn't it sane to assume that it will be a contiguous array?

6 Answers

In JavaScript arrays can be sparse - they can contain "holes". For example

const array = new Array(3);

results in an array of three "holes" - not values. So while

const isInBounds = 0 <= index && index < array.length;

correctly identifies whether index is within bounds on array it does not indicate whether there is a value at array[index].

Object.prototype.hasOwnProperty() can be used to determine whether a value exists at an index. It also needs to be noted that different parts of the language can behave quite differently in the presence of "holes".

// ESLint: no-prototype-builtins)
const hasOwnProperty = Object.prototype.hasOwnProperty;

function show(array, index) {
  const msg =
    0 > index || index >= array.length
      ? `index ${index} is out of bounds`
      : !hasOwnProperty.call(array, index)
      ? `index ${index} is a hole`
      : `index ${index} holds ${array[index]}`;

  console.log(msg);
}

const subject = [undefined, , 1];

show(subject, -1);
// "index -1 is out of bounds"

for (let i = 0; i < subject.length; i += 1) show(subject, i);
// "index 0 holds undefined"
// "index 1 is a hole"
// "index 2 holds 1"

show(subject, 3);
// "index 3 is out of bounds"

const toString = (value) =>
  value !== undefined ? value.toString() : 'undefined';

// for..of doesn't skip holes
const byForOf = [];
for (const value of subject) byForOf.push(toString(value));
console.log(`Values found by for..of: ${byForOf.join(', ')}`);
// "Values found by for..of: undefined, undefined, 1"

// .forEach skips holes
const byForEach = [];
subject.forEach((value) => byForEach.push(toString(value)));
console.log(`Values found by .forEach: ${byForEach.join(', ')}`);
// "Values found by .forEach: undefined, 1"

// .reduce skips holes
const reducer = (acc, value) => {
  acc.push(toString(value));
  return acc;
};
const byReduce = subject.reduce(reducer, []);
console.log(`Values found by .reduce: ${byReduce.join(', ')}`);
// "Values found by .reduce: undefined, 1"

// .map preserves holes
const byMap = subject.map(toString);
console.log(`Values found by .map: ${byMap.join(', ')}`);
// "Values found by .map: undefined, , 1"
Related