Attempted to rewrite getElementByClassName, running into a recursive snap - javascript

As the title mentions, I tried rewriting getElementByClassName as a personal exercise, but am running into some unexpected behavior with my recursive return of results.
Document.prototype.getElementsByClassNameExercise = function(className, tempElement){
var currentElement = (tempElement || document),
children = currentElement.childNodes,
results = [],
classes = [];
// Loop through children of said element
for(var i =0;i<children.length;i++){
if(children[i].className && children[i].className !== '') {
classes = children[i].className.split(' ');
// Important to note, forEach is not ie8 safe.
classes.forEach(function(singleClass){
if(singleClass === className) {
results.push(children[i]);
}
});
}
results.concat(Document.prototype.getElementsByClassNameExercise.call(this, className, children[i]));
}
return results;
}
I attempted this on my homepage, and it appears to successfully parse all DOM elements and find the className... but the return/ results.concat(results) step seem to fail. :/
Any takers can see what I am missing? :)

Your Problem
You're not missing much.
concat() returns a new array, as explained in its MDN article:
Summary
Returns a new array comprised of this array joined with other array(s) and/or value(s).
Description
[...] concat does not alter this or any of the arrays provided as arguments but instead returns a shallow copy that contains copies of the same elements combined from the original arrays.
In case of doubt, you can also always refer to the ECMA-262 specs if the MDN isn't enough, section 15.4.4.4:
When the concat method is called with zero or more arguments item1, item2, etc., it returns an array containing the array elements of the object followed by the array elements of each argument in order.
Solution
You need to-reassign the results variable.
Change this line:
results.concat(Document.prototype.getElementsByClassNameExercise.call(this, className, children[i]));
to:
results = results.concat(Document.prototype.getElementsByClassNameExercise.call(this, className, children[i]));

Related

Does any change in an array gets to change the entire array?

I got into that question by thinking about sorting algorithms.
Does changing an element position inside an array would be the same, at the interpretation, compilation or run-time phases, to recreate the entire array with new elements?
Does it change radically from one language to another?
How does it work?
Imagining a specific case in JavaScript, for example:
let animals = ["monkey","zebra","banana","capybara"];
animals.splice(2,1); // returns ["banana"]
Would it be correct to state that the entire animals array was rewritten? Or is there another type of change type? How would that one and only change work, computation-wise?
It's stated in Javascript documentation that the .splice() method "takes" an array object, creates a new array without it and returns another one with it as an output, but how does it work?
I'm a begginner, so please have patience, and would like a good reading recommendation if possible to learn more about it. Sorry for the incovenience.
Does changing an element position inside an array would be the same, at the interpretation, compilation or run-time phases, to recreate the entire array with new elements?
I think you're asking this:
Does changing an element position inside an array recreate the entire array with new elements?
If you use the array indexer [], then no: the array is not recreated, it's mutated in-place.
If you use Array.prototype.splice, then no: the array is not recreated, it's mutated in-place.
But splice does return a new array containing the removed elements.
If you use Array.prototype.filter and/or Array.prototype.map, then yes: a new array containing only the filtered elements is returned (and the original input array is unmodified at all: i.e. nothing is mutated).
If you use Array.prototype.forEach to assign elements back to itself (which you shouldn't be doing anyway), then no: the array is mutated in-place.
Imagining a specific case in JavaScript, for example:
let animals = ["monkey","zebra","banana","capybara"];
animals.splice(2,1); // returns ["banana"]
Would it be correct to state that the entire animals array was rewritten?
No. That would be incorrect.
The correct thing to say is that "the animals array was mutated".
It's stated in Javascript documentation that the .splice() method "takes" an array object, creates a new array without it and returns another one with it as an output, but how does it work?
I don't know what "JavaScript documentation" you're referring to, but that is incorrect.
The only authoritative sources for JavaScript documentation are either the official ECMAScript specification or documentation provided by JavaScript engine vendors (such as Mozilla's Spidermonkey) documented on MDN.
Sources such as W3Schools are not authoritative.
...and MDN says this about splice (emphasis mine):
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
The splice() method changes the contents of an array by removing or replacing existing elements and/or adding new elements in place.
how does it work?
The inner-workings of the splice function are defined in the ECMAScript specification (section 23.1.3.29).
...which can be summarized as:
Given a splice invocation with arguments ( this: Array, start: int, deleteCount: int );...
Create a new array to hold deleteCount items: deletedItems.
Copy elements in this from index start to start + deleteCount into deletedItems.
Copy elements in this from start + deleteCount to this.length upwards to fill the gap of removed items.
Shrink the length to this.length - deleteCount.
In TypeScript, something like this (ignoring handling of negative arguments):
function splice<T>( this: T[], start: int, deleteCount: int ): T[] {
const removedItems: T[] = [];
const tailIdx = start + deleteCount;
for( let i = start; i < tailIdx; i++ ) {
removedItems.push( this[i] );
}
for( let i = tailIdx, f = start; i < this.length; i++, f++ ) {
this[i] = this[f];
}
this.length = this.length - deleteCount;
}

Sparse Array HackerRank Exercise

I'm trying to resolve some exercises from hacker rank, and I'm struggling with the array map and filter methods.
This function supposes to search a value from queries array into strings array and when I use the map method it returns an array with undefined values, and when I use the filter method it returns an empty array.
var strings = ['ab','ab','bca','acb']
var queries = ['ab','abc','bc']
function matchingStrings(strings, queries) {
let retArr = []
for(let n = 0; n<queries.length; n++){
var equalelem = strings.filter(function(queries){queries[n] === [...strings]})
retArr.push(equalelem.length);
}
return equalelem
}
console.log(matchingStrings(strings, queries))
Looking only at your syntax, and without analyzing the algorithm, the following things should change:
Your filter function always returns undefined. Any function that doesn't have an explicit return statement returns undefined. Maybe you wanted to use an arrow function expression, which doesn't require a return statement if you omit the curly braces?
var equalelem = strings.filter((queries) => queries[n] === [...strings])
Inside the filter function, you seem to be comparing an element from list queries to a copy of list strings. Comparing an element of a list (in this case, a single string) to a list will never be true in your case.
The variable letArr doesn't seem to serve any purpose.

In ES5 Javascript, how do I add an item to an array and return the new array immediately, without using concat?

I often find myself in the situation where I want to, in a single (atomic) operation, add an item to an array and return that new array.
['a', 'b'].push('c');
won't work as it returns the new length.
I know the following code works
['a', 'b'].concat(['c']);
But I find it ugly code (combining two arrays just to add a single item to the end of the first array).
I can't use Array.splice() as it modifies the original array (and returns the removed items). Array.slice() does return a shallow copy but you can't add new items.
ES6
I'm aware that in es6 you can use
[...['a', 'b'], 'c']
But I'm looking for an es5 solution
Lodash
I'm okay in using lodash
Just to be clear
I'm aware that this can be achieved in several different ways (like the Array.concat() method above), but I'm looking for an intuitive simple piece of code, which doesn't "misuses" other operators
I know the following code works ['a', 'b'].concat(['c']); But I find
it ugly code (combining two arrays just to add a single item to the
end of the first array).
The concat() method can be given a single (or multiple) values without the need of encapsulating the value(s) in an array first, for example:
['a', 'b'].concat('c'); // instead of .concat(['c']);
From MDN (my emphasis):
Arrays and/or values to concatenate into a new array.
Besides from that there are limited options without using extension and existing methods.
Example on how to extend the Array (this will return current array though):
Array.prototype.append = function(item) {
this.push(item);
return this
};
var a = [1, 2, 3];
console.log(a.append(4))
Optionally create a simple function as #torazaburo suggests, which can take array and item as argument:
function append(arr, item) {
arr.push(item);
return arr;
}
or using concat():
function append(arr, item) {
return arr.concat(item)
}
I can offer two methods for Array.prototype.insert() which will allow you insert single or multiple elements starting from any index within the array.
1) mutates the array it's called upon and returns it
Array.prototype.insert = function(i,...rest){
this.splice(i,0,...rest)
return this
}
var a = [3,4,8,9];
console.log(JSON.stringify(a.insert(2,5,6,7)));
ES5 compliant version of the above snippet.
Array.prototype.insert = function(i){
this.splice.apply(this,[i,0].concat(Array.prototype.slice.call(arguments,1)));
return this;
};
2) Not mutates the array it's called upon and returns a new one
Array.prototype.insert = function(i,...rest){
return this.slice(0,i).concat(rest,this.slice(i));
}
var a = [3,4,8,9],
b = a.insert(2,5,6,7);
console.log(JSON.stringify(a));
console.log(JSON.stringify(b));
ES5 compliant version of the above snippet.
Array.prototype.insert = function(i){
return this.slice(0,i).concat(Array.prototype.slice.call(arguments,1),this.slice(i));
}

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'));

Most efficient way to convert an HTMLCollection to an Array

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]

Categories

Resources