Change the value of an array changes original array JavaScript - javascript

The following code causes both elements from id 0 to be set to -, even though I want only one to be set to -1. Am I just creating a reference to the labelArray, or is something else?
labelArray.sort(compare);
valueArray = labelArray;
valueArray[0] = '-1';
labelArray[0] = '-';
All help is appreciated.
UPDATE (2019): It's been several years since I first did this post, and ES6 is used pretty much universally. So, I wanted to come back and add that, instead of using the slice() method recommended in the accepted answer, you can instead use array destructing in the following to make a copy:
valueArray = [...labelArray];

Yes. Both valueArray and labelArray reference the same underlying array. To make a copy, use slice():
valueArray = labelArray.slice(0);
NOTE: Slice() only copies 1 level deep, which works fine for primitive arrays. If the array contains complex objects, use something like jQuery's clone(), credit #Jonathan.

Am I just creating a reference to the labelArray […] ?
Yes, exactly. valueArray and labelArray still identify the same object, which hasn't been copied.

valueArray is just a reference to labelArray.
What you want to do is clone the array. You can do this using jQuery.clone() or a similar cloning function.

Related

Javascript filter function - Trying to understand it properly

I'd like some help to clarify how exactly I should be using filter.
The following works just fine:
let nums = [10, 12, 15, 20]
nums.filter(num => num > 14)
result = [15, 20]
If I understand this correctly, I'm passing in a function with num as argument.
Now here's where it all gets confusing (Keep in mind I'm not an advanced js programmer)
I have an array of html elements
let fields = document.getElementsByClassName("f-field")
Every single element in the returned array contains a bunch of other html elements, it looks something like this.
<div class="f-field">
<textarea id="9008" name="Logo"></textarea>
</div>
The inner HTML can be textareas, selects, inputs, whatever...
I tried this and it says
"fields.filter is not a function"
fields.filter(field => field.getElementsByName("Logo"))
I'm assuming that filter does not work for an Array of html elements. Or am I doing this wrong?
Thanks in advance, I'm trying to really understand javascript
DOM query methods like getElementsByClassName and querySelector return collections that are array-like, but not actually arrays (HTMLCollection, NodeList). They have numbered keys you can iterate over, and length properties too, but do not support array generics like filter, forEach or map.
You can cast an array-like object into an array using array = Array.from(source). If you're writing ES6, you could also use the spread operator: array = [...source]
So, you could write your code as follows:
let fields = document.querySelectorAll('.f-field');
logos = Array.from(fields).filter(field => field.getElementsByName('logo'));
Then again, why do all that filtering and traversing when you could just pass a CSS selector straight to querySelectorAll? e.g.
let logos = Array.from(document.querySelectorAll('.f-field [name="logo"]'));
Yup this is a little tricky. getElementsByClassName does NOT return an Array. It returns an HTMLCollection, which behaves like an array in some ways (you can iterate over it and it has a length), but does not contain most of the Array methods.
You can convert to an array as follows
var filteredFields = [...document.getElementsByClassName("f-field")].filter(item => itemTest(item));
Find out more about the spread operator at MDN

Array.prototype.map() and Array.prototype.forEach()

I've an array (example array below) -
a = [{"name":"age","value":31},
{"name":"height (inches)","value":62},
{"name":"location","value":"Boston, MA"},
{"name":"gender","value":"male"}];
I want to iterate through this array of objects and produce a new Object (not specifically reduce).
I've these two approaches -
a = [{"name":"age","value":31},
{"name":"height (inches)","value":62},
{"name":"location","value":"Boston, MA"},
{"name":"gender","value":"male"}];
// using Array.prototype.map()
b = a.map(function(item){
var res = {};
res[item.name] = item.value;
return res;
});
console.log(JSON.stringify(b));
var newObj = [];
// using Array.prototype.forEach()
a.forEach(function(d){
var obj = {};
obj[d.name] = d.value;
newObj.push(obj)
});
console.log(JSON.stringify(newObj))
Is it not right to just use either one for this sort of operations?
Also, I'd like to understand the use case scenarios where one will be preferred over the other? Or should I just stick to for-loop?
As you've already discussed in the comments, there's no outright wrong answer here. Aside from some rather fine points of performance, this is a style question. The problem you are solving can be solved with a for loop, .forEach(), .reduce(), or .map().
I list them in that order deliberately, because each one of them could be re-implemented using anything earlier in the list. You can use .reduce() to duplicate .map(), for instance, but not the reverse.
In your particular case, unless micro-optimizations are vital to your domain, I'd make the decision on the basis of readability and code-maintenance. On that basis, .map() does specifically and precisely what you're after; someone reading your code will see it and know you're consuming an array to produce another array. You could accomplish that with .forEach() or .reduce(), but because those are capable of being used for more things, someone has to take that extra moment to understand what you ARE using them for. .map() is the call that's most expressive of your intent.
(Yes, that means in essence prioritizing efficiency-of-understanding over efficiency-of-execution. If the code isn't part of a performance bottleneck in a high-demand application, I think that's appropriate.)
You asked about scenarios where another might be preferred. In this case, .map() works because you're outputting an array, and your output array has the same length as your input array. (Again; that's what .map() does). If you wanted to output an array, but you might need to produce two (or zero) elements of output for a single element of input, .map() would be out and I'd probably use .reduce(). (Chaining .filter().map() would also be a possibility for the 'skip some input elements' case, and would be pretty legible)
If you wanted to split the contents of the input array into multiple output arrays, you could do that with .reduce() (by encapsulating all of them as properties of a single object), but .forEach() or the for loop would look more natural to me.
First, either of those will work and with your example there's no reason not to use which ever is more comfortable for your development cycle. I would probably use map since that is what is for; to create "a new array with the results of calling a provided function on every element in this array."
However, are you asking which is the absolute fastest? Then neither of those; the fastest by 2.5-3x will be a simple for-loop (see http://jsperf.com/loop-vs-map-vs-foreach for a simple comparison):
var newObj = [];
for (var i = 0, item; item = a[i]; i++) {
var obj = {};
obj[item.name] = item.value;
newObj.push(obj);
});
console.log(JSON.stringify(newObj));

jQuery Get associative array Key from index

My question is the following, in an array:
var array = new Array();
array['abc'] = 'value1';
array['def'] = 'value2';
How do I get the associative key of an array if I have its index number? Let's say I want associative key of arr[0]'s associative key ( => 'abc'), or associative key of arr[1] '=> 'def'). How is this possible in jQuery?
Let's be clear, I am not looking for the value and I do not need to use $.each(). I just need to link 0 to 'abc' and 1 => 'def' etc... Unfortunately something like arr[0].assoc_key() doesn't seem to exist T_T
Thanks a bunch.
All right so the solution is pretty simple, you need to create an object which associates indeces with keys as well as keys with values. Here is a JSBin that works. Please note that to add an element, you need a custom function (addElement in this case) to be able to have both indeces and keys associated at the right places. This is a rough draft to give you an idea of how it can be done!
JSBin
If you have any question or if that wasn't exactly what you expected, simply edit your question and I'll have another glance at it. It HAS to be a custom made object if you want the behavior you asked for.
Javascript doesn't have a native Dictionary type, you would have to write it. – T McKeown
It isn't possible and jQuery doesn't come into the picture at all. If you use an array as a dictionary like that, you are doing something wrong. – Jon
rethink the way you are doing it. Maybe try array[0] = {key: 'abc', value: 'value1'} – Geezer68
#Geezer68, objects do not support multidimentional data, the array I'm working on is 3 levels deep (I know I didn't says so in my original post, but I didn't think it was relevant).
Anyway, thank you guys, it answers the question! I will rethink it then ;-)
EDIT: I guess I'll just add a level:
var array = new Array();
array[] = 'abc';
array[0] = 'value1'
I don't know an other than using a for ... in. So here how i do it and hope you get a better answer (because i want to know aswell!).
var array = new Array();
array['abc'] = 'value1';
array['def'] = 'value2';
var listKeys = [];
for(x in array) listKeys.push(x);
console.log(listKeys); // ['abc', 'def']
but using [string] on an array object is adding property to the object, not the array. So it may be better to initialise it like that :
var array = {};
You might learn more information on this technique in this question and some restriction on why you should not rely on that.

How to merge few 3D JSON objects in JS [or jQuery]

I have to merge 2 (up to 6) JSON objects.
I got this code: http://jsfiddle.net/5Uz27/
But with this code, I can only merge the first level of the objects, so the deeper levels are usually overwritten. Check the output.
How can I fix that?
jQuery.extend(true, original_object, extend_with);
source: http://api.jquery.com/jQuery.extend/
With jQuery, you can use $.extend() to do a "deep"/recursive merge of objects, by passing in true as the first argument.
Here's how this might work in your example:
// turn the strings into objects
var pref_array = $.map(json_holder, JSON.parse);
// add the deep=true argument
pref_array.unshift(true);
// now do a deep extend, passing the array as arguments
var prefs = $.extend.apply(null, pref_array );
This might be a little obtuse (you could make it even more so, but tighter, by setting pref_array to [true].concat($.map(json_holder, JSON.parse))), but it avoids the ungainly for loop (that might be personal preference, I suppose).
Working jsFiddle here: http://jsfiddle.net/e6bnU/

Javascript collection of DOM objects - why can't I reverse with Array.reverse()?

What could be the problem with reversing the array of DOM objects as in the following code:
var imagesArr = new Array();
imagesArr = document.getElementById("myDivHolderId").getElementsByTagName("img");
imagesArr.reverse();
In Firefox 3, when I call the reverse() method the script stops executing and shows the following error in the console of the Web Developer Toolbar:
imagesArr.reverse is not a function
The imagesArr variable can be iterated through with a for loop and elements like imagesArr[i] can be accessed, so why is it not seen as an array when calling the reverse() method?
Because getElementsByTag name actually returns a NodeList structure. It has similar array like indexing properties for syntactic convenience, but it is not an array. For example, the set of entries is actually constantly being dynamically updated - if you add a new img tag under myDivHolderId, it will automatically appear in imagesArr.
See http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-536297177 for more.
getElementsByTag() returns a NodeList instead of an Array. You can convert a NodeList to an Array but note that the array will be another object, so reversing it will not affect the DOM nodes position.
var listNodes = document.getElementById("myDivHolderId").getElementsByTagName("img");
var arrayNodes = Array.slice.call(listNodes, 0);
arrayNodes.reverse();
In order to change the position, you will have to remove the DOM nodes and add them all again at the right position.
Array.prototype.slice.call(arrayLike, 0) is a great way to convert an array-like to an array, but if you are using a JavaScript library, it may actually provide a even better/faster way to do it. For example, jQuery has $.makeArray(arrayLike).
You can also use the Array methods directly on the NodeList:
Array.prototype.reverse.call(listNodes);
this problem can Actually be solved easily with array spread operator.
let elements = document.querySelectorAll('button');
elements = [...elements];
console.log(elements) // Before reverse
elements = elements.reverse(); // Now the reverse function will work
console.log(elements) // After reverse
<html>
<body>
<button>button1</button>
<button>button2</button>
<button>button3</button>
<button>button4</button>
<button>button5</button>
</body>
</html>
getElementsByTag() returns a NodeList instead of an Array. You need to convert the NodeList to an array then reverse it.
var imagesArr = [].slice.call(document.getElementById("myDivHolderId").getElementsByTagName("img"), 0).reverse();
I know this question is old but I think it needs a bit of clarification as some of the answers here are outdated as W3C changed the definition, and consequently the return value of these methods getElementsByTagName() and getElementsByClassName()
These methods as of the time of writing this answer return an object - empty or not - of type HTMLCollection and not NodeList.
It's like the difference between the properties children which returns an object of type HTMLCollection since it's only composed of elements and excluding text or comment nodes, and childNodes which returns an object of type NodeList since it could contain other node types like text and comments as well.
Note: I'd go on tangent here and express my lack of insight on why querySelectorAll() method currently returns a NodeList and not an HTMLCollection since it exclusively works on element nodes in the document and nothing else.
Probably it has something to do with potential coverage of other node types in the future and they went for a more future proof solution, who knows really? :)
EDIT: I think I got the rationale behind this decision to opt for a NodeList and not an HTMLCollection for the querySelectorAll().
Since they constructed HTMLCollection to be exclusively and entirely live and since this method doesn't need this live functionality, they decided for a NodeList implementation instead to best serve its purpose economically and efficiently.
Your first line is irrelevant, since it doesn't coerce the assignment to the variable, javascript works the other way. imagesArr, is not of Type Array(), its of whatever the return type of getElementsByTagName("img") is. In this case, its an HtmlCollection in Firefox 3.
The only methods on this object, are the indexers, and length. In order to work in reverse, just iterate backwards.
This worked for me, I did a reverse for loop and allocated the nodes to an array
var Slides = document.getElementById("slideshow").querySelectorAll('li');
var TempArr = [];
for (var x = Slides.length; x--;) {
TempArr.push(Slides[x]);
}
Slides = TempArr;

Categories

Resources