Dmitry Dmitry - 3 months ago 8
Javascript Question

What is the cleanest way to convert javascript "almost arrays" into "arrays"?

I have been playing around with JavaScript for the past few months and found it to be really refreshing after not using it for many years.

That said, one thing in particular that really bothers me is its' inconsistancies in how it describes "[]" construct.

In particular, I am used to having Arrays have a "map" function, but many things that are "[]"-enclosed do not support map, but often support a synonym, and sometimes don't at all.

Examples:


  1. document.getElementById('my-div').children



Has no mappability at all and each time I need to map over it, I have to resort to for loops.


  1. document.querySelectorAll('#my-div *')



Has
forEach :: callback -> undefined


Unlike map, does not return an array, so chaining transformations is "strange" because in addition to mapping over the object, you also have to store the results in an array to continue.

This means the following won't work.

(() => {
document.querySelectorAll('#my-div *').forEach((divObject) => {
console.log('found a ' + divObject.tagName + '.');
return divObject;
}).forEach((divObject) => {
console.log('more transformations on ' + divObject.tagName + '.');
return divObject;
});
})();


but this will:

(() => {
var children = [];

document.querySelectorAll('#my-div *').forEach((divObject) => {
children.push(divObject);
});

children.map((divObject) => {
console.log('found a ' + divObject.tagName + '.');
return divObject;
}).map((divObject) => {
console.log('more transformations on ' + divObject.tagName + '.');
return divObject;
});
})();


But the copying of elements "one by one" inside a forEach is just extra work that serves no real purpose, and generally should be optimized out before publishing code like this, as it is a detriment to performance for no good reason.


  1. jQuery's map works correctly. eg
    $('#my-div *')
    has a
    map :: callback -> array that supports map operation
    . So it can be chained as expected.



The Question:

Is there a cleaner way to overcome the inconsistancy of array-like interfaces(things that appear to be "Arrays" but do not support Array.prototype.map) than having to do the first-iteration using a for loop, pushing the contents, only after that knowing that it behaves the way you think it behaves?

Answer

For all exotic arrays like node lists etc if they are iterable you can safely do like

var properArray = [...arrayLike] or var properArray = Array.from(arrayish)

or you can even do like

var properarray = Array(arrayish.length),
              i = 0;
for (var key of arrayish) while i < arrayish.length properArray[i++] = arrayish(key);

If you have an object like

var o = {prop_1:"one thing",
         prop_2:"another thing"},
    a = [];

o[Symbol.iterator] = function* (){
                       var oks = Object.keys(this);
                       for (var key of oks) yield this[key]
                     }
a = [...o];
console.log(a);

you can make it iterable and convert it into an array the same way;