Most efficient way to convert an HTMLCollection to an Array - javascript

Is there a more efficient way to convert an HTMLCollection to an Array, other than iterating through the contents of said collection and manually pushing each item into an array?

var arr = Array.prototype.slice.call( htmlCollection )
will have the same effect using "native" code.
Edit
Since this gets a lot of views, note (per #oriol's comment) that the following more concise expression is effectively equivalent:
var arr = [].slice.call(htmlCollection);
But note per #JussiR's comment, that unlike the "verbose" form, it does create an empty, unused, and indeed unusable array instance in the process. What compilers do about this is outside the programmer's ken.
Edit
Since ECMAScript 2015 (ES 6) there is also Array.from:
var arr = Array.from(htmlCollection);
Edit
ECMAScript 2015 also provides the spread operator, which is functionally equivalent to Array.from (although note that Array.from supports a mapping function as the second argument).
var arr = [...htmlCollection];
I've confirmed that both of the above work on NodeList.
A performance comparison for the mentioned methods: http://jsben.ch/h2IFA

not sure if this is the most efficient, but a concise ES6 syntax might be:
let arry = [...htmlCollection]
Edit: Another one, from Chris_F comment:
let arry = Array.from(htmlCollection)

I saw a more concise method of getting Array.prototype methods in general that works just as well. Converting an HTMLCollection object into an Array object is demonstrated below:
[].slice.call( yourHTMLCollectionObject );
And, as mentioned in the comments, for old browsers such as IE7 and earlier, you simply have to use a compatibility function, like:
function toArray(x) {
for(var i = 0, a = []; i < x.length; i++)
a.push(x[i]);
return a
}
I know this is an old question, but I felt the accepted answer was a little incomplete; so I thought I'd throw this out there FWIW.

For a cross browser implementation I'd sugguest you look at prototype.js $A function
copyed from 1.6.1:
function $A(iterable) {
if (!iterable) return [];
if ('toArray' in Object(iterable)) return iterable.toArray();
var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
}
It doesn't use Array.prototype.slice probably because it isn't available on every browser. I'm afraid the performance is pretty bad as there a the fall back is a javascript loop over the iterable.

This works in all browsers including earlier IE versions.
var arr = [];
[].push.apply(arr, htmlCollection);
Since jsperf is still down at the moment, here is a jsfiddle that compares the performance of different methods. https://jsfiddle.net/qw9qf48j/

To convert array-like to array in efficient way we can make use of the jQuery makeArray :
makeArray: Convert an array-like object into a true JavaScript array.
Usage:
var domArray = jQuery.makeArray(htmlCollection);
A little extra:
If you do not want to keep reference to the array object (most of the time HTMLCollections are dynamically changes so its better to copy them into another array, This example pay close attention to performance:
var domDataLength = domData.length //Better performance, no need to calculate every iteration the domArray length
var resultArray = new Array(domDataLength) // Since we know the length its improves the performance to declare the result array from the beginning.
for (var i = 0 ; i < domDataLength ; i++) {
resultArray[i] = domArray[i]; //Since we already declared the resultArray we can not make use of the more expensive push method.
}
What is array-like?
HTMLCollection is an "array-like" object, the array-like objects are similar to array's object but missing a lot of its functionally definition:
Array-like objects look like arrays. They have various numbered
elements and a length property. But that’s where the similarity stops.
Array-like objects do not have any of Array’s functions, and for-in
loops don’t even work!

This is my personal solution, based on the information here (this thread):
var Divs = new Array();
var Elemns = document.getElementsByClassName("divisao");
try {
Divs = Elemns.prototype.slice.call(Elemns);
} catch(e) {
Divs = $A(Elemns);
}
Where $A was described by Gareth Davis in his post:
function $A(iterable) {
if (!iterable) return [];
if ('toArray' in Object(iterable)) return iterable.toArray();
var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
}
If browser supports the best way, ok, otherwise will use the cross browser.

I suppose that calling Array.prototype functions on instances of HTMLCollection is a much better option than converting collections to arrays (e.g.,[...collection] or Array.from(collection)), because in the latter case a collection is unnecessarily implicitly iterated and a new array object is created, and this eats up additional resources. Array.prototype iterating functions can be safely called upon objects with consecutive numeric keys starting from [0] and a length property with a valid number value of such keys' quantity (including, e.g., instances of HTMLCollection and FileList), so it's a reliable way. Also, if there is a frequent need in such operations, an empty array [] can be used for quick access to Array.prototype functions; or a shortcut for Array.prototype can be created instead. A runnable example:
const _ = Array.prototype;
const collection = document.getElementById('ol').children;
alert(_.reduce.call(collection, (acc, { textContent }, i) => {
return acc += `${i+1}) ${textContent}` + '\n';
}, ''));
<ol id="ol">
<li>foo</li>
<li>bar</li>
<li>bat</li>
<li>baz</li>
</ol>

Sometimes, Even You have written code the correct way, But still it doesn't work properly.
var allbuttons = document.getElementsByTagName("button");
console.log(allbuttons);
var copyAllButtons = [];
for (let i = 0; i < allbuttons.length; i++) {
copyAllButtons.push(allbuttons[i]);
}
console.log(copyAllButtons);
you get empty array.
Like, This
HTMLCollection []
[]
Console_javascript
For Solving this problem, You have to add link of javascript file after body tag in html file.
<script src="./script.js"></script>
As you can see below,
html_file
Final Output
HTMLCollection(6) [button.btn.btn-dark.click-me, button.btn.btn-dark.reset, button#b, button#b, button#b, button#b, b: button#b]
(6) [button.btn.btn-dark.click-me, button.btn.btn-dark.reset, button#b, button#b, button#b, button#b]

Related

javascript check existence of elements of 1 array inside the other

For searching elements of one array inside the other, one may use the indexOf() on the target of search and run a loop on the other array elements and in each step check existence. This is trivial and my knowledge on Javascript is too. Could any one please suggest a more efficient way? Maybe even a built-in method of the language could help? Although I couldn't hit to such a method using google.
You can use Array.filter() internally and implement a function on Array's prototype which returns elements which are common to both.
Array.prototype.common = function(a) {
return this.filter(function(i) {
return a.indexOf(i) >= 0;
});
};
alert([1,2,3,4,5].common([4,5,6])); // "4, 5"
Again as you mention in your post, this logic also works by taking each element and checking whether it exists in the other.
A hair more efficient way is convert one of the arrays into a hash table and then loop through the second one, checking the presence of elements at O(1) time:
a = [1,2,3,4,5]
b = [1,7,3,8,5]
map = {}
a.forEach(function(x) { map[x] = 1 })
intersection = b.filter(function(x) { return map[x] === 1 })
document.write(JSON.stringify(intersection))
This only works if elements in question are primitives. For arrays of objects you have to resort to the indexOf method.
ES6 ("Harmony") does support Set, but strangely not set operations (union, intersection etc), so these should be coded by hand:
// Firefox only
a = [1,2,3,4,5]
b = [1,7,3,8,5]
sa = new Set(a)
sb = new Set(b)
sa.forEach(function(x) {
if (!sb.has(x))
sa.delete(x);
});
document.write(uneval([...sa]))
JavaScript's arrays don't have a method that does intersections. Various libraries provide methods to do it (by looping the array as you describe), including Underscore and PrototypeJS (and others, I'm sure).

Merging two object (NodeList) arrays in JavaScript

I am attempting to merge two arrays of objects to so I can validate a form. The usual concat method does not appear to work in this circumstance. Concat works with ordinary numerical and string arrays but doesn't with object arrays. The line var allTags = allInputs.concat(allSelects); does not work.
var allInputs = document.getElementsByTagName("input");
alert("Inputs: " + allInputs.length);
var allSelects = document.getElementsByTagName("select");
alert("Selects: " + allSelects.length);
var allTags = allInputs.concat(allSelects);
alert("allTags: " + allTags.length);
Concat works with ordinary numerical and string arrays but doesn't with object arrays.
Actually, it does, but NodeList instances don't have a concat method, and Array#concat doesn't have a means of identifying that you want to flatten those (because they're not arrays).
But it's still fairly easy to do (see caveat below, though). Change this line:
var allTags = allInputs.concat(allSelects);
to
var allTags = [];
allTags.push.apply(allTags, allInputs);
allTags.push.apply(allTags, allSelects);
Live Example | Source
That works by using a bit of a trick: Array#push accepts a variable number of elements to add to the array, and Function#apply calls the function using the given value for this (in our case, allTags) and any array-like object as the arguments to pass to it. Since NodeList instances are array-like, push happily pushes all of the elements of the list onto the array.
This behavior of Function#apply (not requiring the second argument to really be an array) is very clearly defined in the specification, and is well-supported in modern browsers.
Sadly, IE6 and 7 don't support the above (I think it's specifically using host objects — NodeLists — for Function#apply's second argument), but then, we shouldn't be supporting them, either. :-) IE8 doesn't, either, which is more problematic. IE9 is happy with it.
If you need to support IE8 and earlier, sadly, I think you're stuck with a boring old loop:
var allInputs = document.getElementsByTagName('input');
var allSelects = document.getElementsByTagName('select');
var allTags = [];
appendAll(allTags, allInputs);
appendAll(allTags, allSelects);
function appendAll(dest, src) {
var n;
for (n = 0; n < src.length; ++n) {
dest.push(src[n]);
}
return dest;
}
Live Example | Source
That does work on IE8 and earlier (and others).
document.get[something] returns a NodeList, which looks very similar to an Array, but has numerous distinctive features, two of which are:
It does not contain the concat method
The list of items is live. This means they change as the document changes in real time.
If you don't have any issues with turning your NodeLists into actual arrays, you could do the following to achieve the effect you desire:
var allInputs = document.getElementsByTagName("input")
, allSelects = document.getElementsByTagName("select")
;//nodeLists
var inputList = makeArray(allInputs)
, selectList = makeArray(allSelects)
;//nodeArrays
var combined = inputList.concat(selectList);
function makeArray(list){
return Array.prototype.slice.call(list);
}
You will lose any and all behaviors of the original NodeList, including it's ability to update in real time to reflect changes to the DOM, but my guess is, that's not really an issue.
What you're dealing with are HTMLCollections, and they're live collections (i.e., when you add a node to the DOM, it's automatically added to the collection.
And sadly, it doesn't have a concat method... for a reason. What you need to do is to convert it to an array, losing its live behaviour:
var allInputsArray = [].slice.call(allInputs),
allSelectsArray = [].slice.call(allSelects),
allFields = allInputsArray.concat(allSelectsArray);
I found a simple way to collect dom objects in the order they appear on a page or form.
Firstly, create a dummy class style 'reqd' on the object then use the following which creates a list in the order they appear on the page. Just add the class to any objects you wish to collect.
var allTags = document.querySelectorAll("input.reqd, select.reqd");
Looked into underscore.js at all? You could do
var allSelectsAndInputs = _.union(_.values(document.getElementsByTagName("input")),_.values(document.getElementsByTagName("select")));
For more information see: http://underscorejs.org/#extend
The function is intended for objects, but works in this setting.

javascript array push problem

I have the following javascript code:
var objectArray = [];
var allInputObjects = [];
var allSelectObjects = [];
var allTextAreaObjects = [];
//following returns 3 objects
allInputObjects = document.getElementById("divPage0").getElementsByTagName("INPUT");
//following returns 1 object
allSelectObjects = document.getElementById("divPage1").getElementsByTagName("SELECT");
//following returns 0 objects
allTextAreaObjects = document.getElementById("divPage2").getElementsByTagName("TEXTAREA");
//and following statement does not work
objectArray = allInputObjects.concat(allSelectObjects);
And my problem is that the last line is throwing an error.
I tried the above code in Firefox and it says allInputObjects.concat is not a function.
Any clues, I believe the script is not treating allInputObjects as an Array!
Any help will be appreciated.
getElementsByTagName returns a NodeList, which is similar to an Array except that it does not support all those prototype functions.
To seamlessly convert such an array-like object into an Array, use:
var arr = Array.prototype.slice.call(somenodelist, 0);
arr will almost be identical, except that it now has the Array prototype functions supported, like concat.
What the function actually does is returning a partial Array containing the elements of somenodelist, to be precise everything from index 0 and after. Obviously, this are just all elements, therefore this is a trick to convert array-like objects into real Arrays.
Why do you think that allSelectObjects is an array?
It is initially assigned to an empty array, sure. But then it's overwritten by the getElementsByTagName call on line 6(ish). Variables in Javascript are not strongly typed, so the initial assignment does not force later assignments to also be arrays.
I suspect you'll have some type of NodeList or similar, rather than an array, in the variable when you invoke the final line. (That's what's returned by the method in Firefox, at least.)
As Andezej has pointed out, you're not dealing with an array here, you're dealing with a kind of node list.
Here's one way you can create an array to work with based on the result of getElementsByTagName
var tags = obj.getElementsByTagName('input');
for (var j=0;j<tags.length;j++) {
resultArray.push(tags[j]);
}
Just another funny way to convert your NodeList to an array:
var tags = obj.getElementsByTagName('input'),
tags2Arr = (
function toArr(i){
return i ? toArr(i-1).concat(tags[i]) : [tags[0]];
}(tags.length-1)
);
Now if you add a method to the Array.prototype:
Array.prototype.clone = function() {
var arr = this;
return (function clone(i){
return i ? clone(i-1).concat(arr[i]) : [arr[0]];
})(this.length-1);
};
You can convert a NodeList to an array, using this oneliner:
Array.prototype.clone.call(obj.getElementsByTagName('input'));

A better way to splice an array into an array in javascript

Is there a better way than this to splice an array into another array in javascript
var string = 'theArray.splice('+start+', '+number+',"'+newItemsArray.join('","')+'");';
eval(string);
You can use apply to avoid eval:
var args = [start, number].concat(newItemsArray);
Array.prototype.splice.apply(theArray, args);
The apply function is used to call another function, with a given context and arguments, provided as an array, for example:
If we call:
var nums = [1,2,3,4];
Math.min.apply(Math, nums);
The apply function will execute:
Math.min(1,2,3,4);
UPDATE: ES6 version
If you're coding in ES6, you can use the "spread operator" (...).
array.splice(index, 0, ...arrayToInsert);
To learn more about the spread operator see the MDN documentation.
The 'old' ES5 way
If you wrap the top answer into a function you get this:
function insertArrayAt(array, index, arrayToInsert) {
Array.prototype.splice.apply(array, [index, 0].concat(arrayToInsert));
}
You would use it like this:
var arr = ["A", "B", "C"];
insertArrayAt(arr, 1, ["x", "y", "z"]);
alert(JSON.stringify(arr)); // output: A, x, y, z, B, C
You can check it out in this jsFiddle: http://jsfiddle.net/luisperezphd/Wc8aS/
This question is really old, but with ES6, there's a simpler way to do this using the spread operator:
sourceArray.splice(index, 0, ...insertedArray)
If you're using uncompiled javascript in the browser, be sure to check if it's supported in your target browser at https://kangax.github.io/compat-table/es6/#test-spread_(...)_operator.
Also, this may be slightly off topic, but if you don't want or need to modify the original array, but could use a new array instead, consider this approach:
mergedArray = sourceArray.slice(0, index).concat(insertedArray, sourceArray.slice(index))
You can also add such a function to the Array prototype, if you want something that is almost identical to the splice method. E.g.
Array.prototype.spliceArray = function(index, n, array) {
return Array.prototype.splice.apply(this, [index, n].concat(array));
}
Then usage would simply be:
var array = ["A","B","C","","E","F"];
array.splice(3,1,"D");
// array is ["A","B","C","D","E","F"]
array.spliceArray(3,3,["1","2","3"]);
// array is ["A","B","C","1","2","3"]
See it in action here: http://jsfiddle.net/TheMadDeveloper/knv2f8bb/1/
Some notes:
The splice function modifies the array directly, but returns the an array of elements that were removed... not the spliced array.
While it's normally not recommended to extend core javascript classes, this is relatively benign with most standard frameworks.
Extending Array won't work in cases where specialized array classes are used, such as an ImageData data Uint8ClampedArray.
The answers above that involve splice.apply and insert the array in a one liner will blow up the stack in a stack overflow for large array.
See example here:
http://jsfiddle.net/gkohen/u49ku99q/
You might have to slice and and push each item of the inserted and remaining part of the original array for it to work.
See fiddle: http://jsfiddle.net/gkohen/g9abppgy/26/
Array.prototype.spliceArray = function(index, insertedArray) {
var postArray = this.splice(index);
inPlacePush(this, insertedArray);
inPlacePush(this, postArray);
function inPlacePush(targetArray, pushedArray) {
// Not using forEach for browser compatability
var pushedArrayLength = pushedArray.length;
for (var index = 0; index < pushedArrayLength; index++) {
targetArray.push(pushedArray[index]);
}
}
}
There are a lot of clever answers here, but the reason you use splice is so that it puts the elements into the current array without creating another. If you have to create an array to concat() against so you can use apply() then you're creating 2 additional trash arrays! Sorta defeats the whole purpose of writing esoteric Javascript. Besides if you don't care about that memory usage stuff (and you should) just dest = src1.concat(src2); it is infinitely more readable. So here's is my smallest number of lines while staying efficient answer.
for( let item of src ) dest.push( item );
Or if you'd like to polyfill it and have a little better browser support back:
src.forEach( function( x ) { dest.push(x); });
I'm sure the first is more performant (it's a word ;), but not supported in all browsers out there in the wild.
If you don't want to concatenate inserting items to first two parameters of Array.splice(),
an elegant way is to use Function.bind() and Function.apply() together.
theArray.splice.bind(null, startIndex, deleteCount).apply(newItemsArray);
I wanted to have a function which would take only part of the source array so I have mine slightly different
based off CMS's answer
function spliceArray(array, index, howmany, source, start, end) {
var arguments;
if( source !== undefined ){
arguments = source.slice(start, end);
arguments.splice(0,0, index, howmany);
} else{
arguments = [index, howmany];
}
return Array.prototype.splice.apply(array, arguments)
}
Array.prototype.spliceArray = function(index, howmany, source, start, end) {
return spliceArray(this, index, howmany, source, start, end);
}
You can see it at: https://jsfiddle.net/matthewvukomanovic/nx858uz5/

Why is 'for(var item in list)' with arrays considered bad practice in JavaScript?

Given a simple zero based, numerically indexed array:
var list = ['Foo', 'Bar', 'Baz'];
Many times, I have noticed that when someone suggests looping through variables in an array like this:
for(var item in list) { ... }
...there's almost certainly someone suggesting that that's bad practice and suggests an alternative approach:
var count = list.length;
for(var i = 0; i < count; i++) {
var item = list[i];
...
}
What's the reasoning for not using the simpler version above and to use the second example instead?
First, the order of the loop is undefined for a for...in loop, so there's no guarantee the properties will be iterated in the order you want.
Second, for...in iterates over all enumerable properties of an object, including those inherited from its prototype. In the case of arrays, this could affect you if your code or any library included in your page has augmented the prototype of Array, which can be a genuinely useful thing to do:
Array.prototype.remove = function(val) {
// Irrelevant implementation details
};
var a = ["a", "b", "c"];
for (var i in a) {
console.log(i);
}
// Logs 0, 1, 2, "remove" (though not necessarily in that order)
Speed?
for(..;..;..) loop proved to be 36 times faster than for .. in when I tested it here.
Link courtesy this SO answer
for ... in ... doesn't return items of list, but instead enumerates array properties.
For that reason alone, it cannot act as a replacement of for (i=0; i<arr.length; i++) loop.
The appropriate alternative is for ... of ... construct. It enumerates values of an iterable object, such as an array. You can read more about it on MDN Web Docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
It's supported by the relevant modern browsers (Internet Explorer doesn't count, with it being replaced by Microsoft Edge). If you can afford not supporting older browsers, it's probably the way to go. You can check the convenient browser support table at the end of aforelinked MDN page to see which browser versions actually allow for ... of ... usage.
If you use for/in like that, item enumerates through string values "0", "1", ..., so not the actual objects in the list. So the the 'item' in the first snippet is more like the i in the second snippet,not the item. Furthermore string values are enumerated where you'd expect numbers. And you get in trouble when you properties to the list, like array.ID = "a123", as they will get enumerated also.
But with these downsides, I still think the syntax is very useful, if your team is aware of what it does.
Add list.foo = bar; and try to use simple for.
If you don't use some libraries(like prototypeJs) and don't add any new properties to array object - you can use simple for-statement.

Categories

Resources