JavaScript Array Polyfills

After a lot of searching I was unable to find polyfills published on the web for the Array.entries(), Array.keys() and Array.values() methods,

So here are my versions:

 if (![].entries) {
   Array.prototype.values = function() {
       var k, a = [], nextIndex = 0, ary = this;
       k = ary.length;
       while (k > 0) a[--k] = [k,ary[k]];
       a.next = function(){
           return nextIndex < ary.length ?
               {value: [nextIndex++,ary[nextIndex]], done: false} :
               {done: true};
       };
   return a;
   };
}

if (![].keys) {
   Array.prototype.keys = function() {
       var k, a = [], nextIndex = 0, ary = this;
       k = ary.length;
       while (k > 0) a[--k] = k;
       a.next = function(){
           return nextIndex < ary.length ?
               {value: nextIndex++, done: false} :
               {done: true};
       };
   return a;
   };
}

if (![].values) {
   Array.prototype.values = function() {
       var k, a = [], nextIndex = 0, ary = this;
       k = ary.length;
       while (k > 0) a[--k] = ary[k];
       a.next = function(){
           return nextIndex < ary.length ?
               {value: ary[nextIndex++], done: false} :
               {done: true};
       };
   return a;
   };
}

You are most welcome to copy these. Suggestions for improvements are also welcome.

Nice. These can be useful.

A few suggestions on style:

  1. Use meaningful names. Unnecessary abbreviations (ary) don’t help, and single letter variables (k, a) definitely don’t help. Readable code is better than short code.

  2. Avoid increments and decrements inside larger expressions, especially when the variable is used more than once within that expression (a[–k] = k, [nextIndex++,ary[nextIndex]]). Clarity, not cleverness, makes code better, and in that expression, it’s not obvious which k will be evaluated first. This is like one of those trivia interview questions where you’re shown ++i = i++ and asked what the value of i will be, to which I think the only correct answer is: I don’t care; don’t write code that way.

  3. In the keys() method, the while loop and the k variable don’t seem at all necessary. Double check the other methods for the same sort of problem.

  4. I think you misnamed your entries method as “values”. Line 2.

  5. values() could probably be implemented without copying the entire array. Since this method could be used on any array, from the very small to the very large, copying the whole thing could be expensive.

EDIT: To reinforce point #2, the [nextIndex++,ary[nextIndex]] causes your entries() method to produce incorrect results.

Only fair that I pony up my own attempt. Here’s how I would write it:

(function () {
    function ArrayIterator(array, kind) {
        this._array = array;
        this._kind = kind;
        this._nextIndex = 0;
    }

    ArrayIterator.prototype.next = function () {
        var currentIndex = this._nextIndex;

        // Are we done?
        if (currentIndex >= this._array.length) {
            return {'value': undefined, 'done': true};
        }

        // Get ready for next time
        this._nextIndex++;

        // Iterating keys?
        if (this._kind === 'key') {
            return {'value': currentIndex, 'done': false};
        }

        // Iterating values?
        if (this._kind === 'value') {
            return {'value': this._array[currentIndex], 'done': false};
        }

        // Iterating entries?
        if (this._kind === 'key+value') {
            return {'value': [currentIndex, this._array[currentIndex]], 'done': false};
        }
    };

    if (!Array.prototype.keys) {
        Array.prototype.keys = function () {
            return new ArrayIterator(this, 'key');
        };
    }

    if (!Array.prototype.values) {
        Array.prototype.values = function () {
            return new ArrayIterator(this, 'value');
        };
    }

    if (!Array.prototype.entries) {
        Array.prototype.entries = function () {
            return new ArrayIterator(this, 'key+value');
        };
    }
}());

Where in your code does it convert a sparse array to a dense array?

There’s no need to do any conversion. Just by iterating every index from 0 to the length ensures we hit even the “holes”. To be extra sure, I re-tested it just now in Chrome and Firefox, and I got correct results.

So looking at an example from MSN:

var arr = ["a", , "c"];
var denseKeys = [...arr.keys()];

The closest equivalent to [...arr.keys()] that I can find that will run across all browsers is Array.apply(null,arr.keys()) but that returns an empty array rather than [1,2,3].

So what ES5 compatible code should I be using for that test in order to get the correct result?

Because that isn’t equivalent to “…”. The equivalent of iterating an object within an array literal:

var denseKeys = [];
for (var iterator = arr.keys(), result = iterator.next(); !result.done; result = iterator.next()) {
    denseKeys.push(result.value);
}

Or, if you want to keep it super simple:

var denseKeys = arr.keys();
console.log(denseKeys.next().value, denseKeys.next().value, denseKeys.next().value);  // [0, 1, 2]

Thanks Jeff.

I managed to totally confuse myself and the original version that I wrote before rewriting it to what I put i the original post in this thread would appear to have been correct. I tried to overthink what was needed and managed to totally screw things up in the process.

Restoring the three separate polyfills back to what I originally had gives me:

if (![].entries) {
   Array.prototype.entries = function() {
       var k = 0, o = this;
       return {
          next: function(){
           return k < o.length ?
               {value: [k,o[k++]], done: false} :
               {done: true};
          }
       };
   };
}

if (![].keys) {
   Array.prototype.keys = function() {
       var k = 0, o = this;
       return {
          next: function(){
           return k < o.length ?
               {value: k++, done: false} :
               {done: true};
          }
       };
   };
}

if (![].keys) {
   Array.prototype.keys = function() {
       var k = 0, o = this;
       return {
          next: function(){
           return k < o.length ?
               {value: k++, done: false} :
               {done: true};
          }
       };
   };
}

that gets rid of the typos I made in converting that to try to also return the array and while it still uses the short variable names, each is still only a few lines long and so should be relatively easy to follow even with short names.

As each is intended to be usable separately (and is on a different page on my site) I can’t see the benefit in my making the iterator part common - although it probably would be if I didn’t need to keep them separate.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.