Why do map, every, and other array functions skip empty values?

The built in iteration functions (as described by others and defined in the specs) will skip values when HasProperty is false.

You could create your own shim for all which would check each value. This would be an expansion of the prototype. Alternatively, turning it into a function if this code were to be used in a wider scope would be a better design and require a slightly different call.

const arr1 = [, , 3, 4];
const arr2 = [1, 2, 3, 4];

Array.prototype.all = function(callback){
 for(let i = 0; i < this.length; i++){
     if(callback(this[i],i,this)) return false;
 }
 return true;
};

const result2 = arr1.all((item, index) => item === arr2[index]);
console.log(result2); // false

Because the language design says so. 🤷🏻‍♂️

See the specification which says:

  • Repeat, while k < len
    • Let Pk be ToString(k).
    • Let kPresent be HasProperty(O, Pk).
    • ReturnIfAbrupt(kPresent).
    • If kPresent is true, then

… then do the operation.

Since a value was never assigned to the 0 and 1 properties, the HasProperty test gives false so they are skipped over by the If rule.


By docs of .every():

callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values.

So, you are calling .every() with just truthy values of array1:

const arr1 = [, , 3, 4]

arr1.every((x, idx) => {
 console.log(`element: ${x}`, `index: ${idx}`);
 return true;
})

It's an extension of the fact that forEach only visits elements that actually exist. I don't know that there's a deeper "why" for that other than that it didn't make much sense to call the callback for a missing element.

You can realize those elements (if that's the world) by using:

  1. Spread notation, or
  2. Array.from, or
  3. Array.prototype.values, or
  4. Array.prototype.entries

...or possibly some others.

const a = [, , 3];
console.log(a.hasOwnProperty(0)); // false
const b = [...a];
console.log(b.hasOwnProperty(0)); // true
const c = Array.from(a);
console.log(b.hasOwnProperty(0)); // true

Applying that to your function with Array.from:

const arr1 = [, , 3, 4]
const arr2 = [1, 2, 3, 4]
const result = Array.from(arr1).every((item, index) => item === arr2[index])
console.log(result) // false

Of course, that involves creating a new array and looping through the previous one copying over the elements. You might be better off with your own for loop.

Applying Array.prototype.entries to your function:

const arr1 = [, , 3, 4]
const arr2 = [1, 2, 3, 4]
let result = true;
for (const [index, value] of arr1.entries()) {
    if (value !== arr2[index]) {
        result = false;
        break;
    }
}
console.log(result) // false

Tags:

Javascript