I'm quite new to coding html/js. I checked answers on here but either can't understand them well enough or they aren't quite what I'm looking for.
The problem is straight forward enough; if I have the following object;
var gadget = {"1":{"id":A, "name":"Rawr"},"2":{"id":B, "name":"GoGoGadget"}"1":{"id":C, "name":"Extendable Arms!"}};
Now, if I wanted to use a forloop (for whatever reason) to extract the names of these objects I would like to try;
var i = 0;
var names = [];
for (i = 0; i < gadget.length; i++) {
names.push(gadget.i.name);
}
I'm not surprised that this doesn't work as the "i" would probably got interpreted as the string "i" here rather than it's numeral. Unfortunately I've tried a few variants that I've found online (like using names.push(gadget[i].name) which also shouldn't work since that suggests gadget is a vector and not an object) but haven't been able to figure out how to get it to work.
Is there somewhere I can find this syntax? Or is this one of those things that seems like it should be easy but js just doesn't really have a nice solution for?
Thanks!
edit:
I didn't mean to suggest I always wanted every entry in the vector, so to clarify my question further, what if I wanted to use a forloop to only find the names of gadget entries given in another object; ie given;
var searchvec = [{"id":1,"count":17},{"id":3,"count":12}];
var i = 0;
var names = [];
for (i = 0; i < searchvec.length; i++) {
index = searchvec.i.id;
names.push(gadget.index.name);
}
I think this is what you are looking for:
var gadget = {"1": {"id": "A", "name": "Rawr"}, "2": {"id": "B", "name": "GoGoGadget"}, "3": {"id": "C", "name": "Extendable Arms!"}};
let names = [];
for (let i in gadget) {
// i is String
names.push(gadget[i].name);
}
for (let i in names) {
// i is int value
console.log(names[i]);
}
Using the for (let x IN o) js takes care for you no matter wether it is an object or an array.
With array, the for loop will cycle through the the array indexes, with x being an int value, and with objects it will cycle through the properties of the object, with x being the property name.
See reference:
MDN
Javascript is a really quirky language and can cause you serious hitches if you are used to more rigorous language. In js an array is actually an object with numbers as keys + some other goodies (such as a push() method) (to see it you may try in a console: typeof []))
P.S. this means you can actually access object properties with obj[key] being key a variable, even null-valued.
You can do something like below:
var names = [];
for (let [key,value] of Object.entries(gadgets)){
names.push(value)
}
Related
While working on building a list of sheet names, I came across this question:
List names of sheets in Google Sheets and skip the first two
Saving you the click, this person's solution is: (Stripped down, pseudo code)
getSheets() // Get all the sheets in spreadsheet workbook
var out = new Array( sheets.length+1 ) ;
for (var i = 1 ; i < sheets.length+1 ; i++ )
out[i] = [sheets[i-1].getName()];
return out
My solution would have leveraged:
...
var sheetName = sheet[i].getName();
out.push(sheetName);
The first solution seems to dynamically create empty array values, then later declare their value. While I have always just pushed new values into the array.
What is the difference?
In which situations is one better than the other?
In which situations should either be avoided?
Your code and the original code do quite different things.
Assuming sheets has objects in it that return the names "sheet1", "sheet2", and "sheet3" from getName, the original code creates an array that looks like this:
[
(missing),
["sheet1"],
["sheet2"],
["sheet3"]
]
Note two things:
There is no entry at index 0. (It literally doesn't exist at all, which is subtly different from existing and containing the value undefined.)
The other entries are single-element arrays, each containing its sheet name.
Your code creates this instead:
[
"sheet1",
"sheet2",
"sheet3"
]
Presumably the author had a reason for skipping index 0 and creating subordinate arrays (I'd guess because they were passing that result array into some API function that expects something in that form).
So there's no really a "better" or "worse" here, just different.
If your fundamental question is whether this:
var original = ["one", "two", "three"];
var updated = [];
for (var i = 0; i < original.length; ++i) {
updated[i] = original[i].toUpperCase(); // Or whatever
}
is better/worse than this:
var original = ["one", "two", "three"];
var updated = [];
for (var i = 0; i < original.length; ++i) {
updated.push(original[i].toUpperCase()); // Or whatever
}
the answer is: It's really a matter of style. Performance isn't markedly different between the two (and rarely matters), and amusingly one way is faster on some JavaScript engines and the other way is faster on others.
Both of those can probably be better expressed using map:
var original = ["one", "two", "three"];
var updated = original.map(function(entry) { return entry.toUpperCase(); });
I think Google Sheets has map, even though it mostly has only ES3-level features otherwise.
Side note: new Array is almost never the right way to create an array.
I have a bizarre issue happening here. I am retrieving data from a remote source and I need to reformat it for better internal use
The data structure is like the following:
clubs = [
0: {
propA: "blah",
probB: "bar",
events: [
0: {
data1: "foo",
data2: "bar"
}
1: {
data1: "this",
data2: "that"
}
1: {
propA: "hello",
probB: "bye",
events [
0: { ...}
1: {...}
...
...
I am looping through clubs and I want to assign the values of each club into its own events as property clubInfo. When I do that though, clubInfo becomes and endless chain of itself. So I delete the new clubInfo.events - and then everything but the root club[0] gets wiped out. I hope I am being clear, its hard to explain.
If I do this:
for (var i=0; i<clubs.clubs.length;i++) {
var clubInfo = clubs.clubs[i] ;
var events = clubs.clubs[i].events ;
for (var j=0; j<events.length; j++) {
}
}
Then clubs becomes:
clubs [
0: events [
clubInfo [
0: events [
clubInfo [
0: events [
....and goes on seemingly forever
]
]
]
]
]
]
If I do:
for (var i=0; i<clubs.clubs.length;i++) {
var clubInfo = clubs.clubs[i] ;
delete clubInfo.events ;
var events = clubs.clubs[i].events ; // events is now empty
for (var j=0; j<events.length; j++) { // errors here because there is no length after the delete
}
}
Then all that remains of clubs is, in fact none of the other properties that have arrays events or others (several of them) are all gone.:
clubs [
0: {
propA: "blah",
probB: "bar"
}
]
Instead of delete, i have even tried just nulling clubInfo.events = null - but the same issue happens, everything gets wiped out.
Oh boy, you've snagged one of JavaScript's current, and most obvious flaws by effectively using this:
clubs[i].event[j].clubInfo = clubs[i];
You're creating an infinite reference - what do I mean by that? It's better displayed through an Array, if you'll oblige me:
let a=[]; a.push([a]);
This creates an infinite level array through self-reference, creating an incalculable depth. You see, though there's a 32(2^32-1) bit limit to an Array's length. This can be demonstrated easily:
Array(2**32); //Uncaught RangeError: Invalid array length
Presumably this was done to prevent browser memory from shorting but, strangely, there was never any consideration to the depth an array may contain. A minor side effect of this is that there is no depth property, but a major side effect is that there is no protection from an infinite self-referencing array.
Getting Around It
The best way to get around this type of situation is to construct a new Object and assign properties from the old Object to the new. You can think of this as cloning. To do this you can utilize the assign method:
Object.assign(constructor, **Array**/**Object**)
Example:
let a = []; a.push(Object.assign([], a));
Problem solved, right? uhhh... not quite Even though this can sometimes work, this still won't fix the issue of an Array or Object with more than shallow references. To get around that you have to use a combination of:
JSON.stringify(obj);
to break references
JSON.parse(JSON);
to remake your object, and
delete obj[deepReference];
deletion to stop any unforeseen issues with any superfluous data/references
None of this is ideal, but currently there is no way to completely separate all references inside of an object or array without recursive iteration.
To give you an example - In your case you're going to want to do something like this:
for (var i = 0; i < clubs.length; i++) {
var clubInfo = clubs[i];
var events = clubs[i].events;
for (var j = 0; j < events.length; j++) {
let jsonTranslation = Object.assign({}, clubs[i]);
delete jsonTranslation.events;
jsonTranslation = JSON.stringify(jsonTranslation);
clubs[i].events[j].clubInfo = JSON.parse(jsonTranslation);
}
}
let clubs = [{
propA: "blah",
probB: "bar",
events: [{
data1: "foo",
data2: "bar"
},
{
data1: "this",
data2: "that"
}
]
}];
for (var i = 0; i < clubs.length; i++) {
var clubInfo = clubs[i];
var events = clubs[i].events;
for (var j = 0; j < events.length; j++) {
let jsonTranslation = Object.assign({}, clubs[i]);
delete jsonTranslation.events;
jsonTranslation = JSON.stringify(jsonTranslation);
clubs[i].events[j].clubInfo = JSON.parse(jsonTranslation);
}
}
console.log(clubs);
Additional Info: Other watch outs
Similarly there are other issues in the language. A badly implemented Array constructor method. Array(n) returns an Array with n members. Why's that an issue? Everything in JavaScript that is declared and not instantiated is undefined except the members of a freshly constructed array. They return empty. The issue with that is this means they have no mappable values. Ergo, all those sweet new functional ES Array methods are useless until the Array is filled. As an example:
Array(3).map((m, i) => i);
This results in well... the same thing you started with — when clearly it should provide a numbered array from 0-2. This is not as big of a deal as an infinite reference because you can work around it like this:
Array(3).fill(0).map((m,i) => i);
But it's effectively a wasted method call to take care of a problem that should be handled within construction.
Lastly, the fill method — give it an object or an Array and it doesn't create n individual object members. It creates n references to a singular array or object and stuffs them into one array. At base logic, it sort of makes sense. As #FelixKling pointed out in the comments, it is fairly consistent with what you would expect, a.e. fill this array with this one thing. I still would debate a bit about it's functionality for two reasons.
In what situation would anyone need n references stored in an Array to the same place in memory? Probably never. How often do people need an Array of similarly constructed Objects? All the time.
When passing an Object Literal for instance ( .fill({a:1}) )I can see the logic of filling the Array with references, even if it may not be wholly intuitive. If passed a Constructor- I would contest that it might make more sense to instantiate each member individually.
So there are many nuances, and inconsistencies with JavaScript that require knowledge to work around — and sadly, infinite reference, is one of them - but on the plus side the only way to typically realize these issues exist is to run into them headlong, so be thankful it wasn't in production!
Hope this helps! Happy Coding!
I am trying to make an array of objects, some being copies of other objects. I need to be able to modify those objects individually later on though.
Here is what I am doing at the moment...
var fruit = [
{ type="banana", copies="3" },
{ type="apple", copies="2" },
]
Later I take all that fruit and put it in a basket.
for (i=0; i < fruit.length; i++){
for(x=0; x < fruit[i].copies; x++){
basket.push(fruit[i]);}}
At this point I can get a list of all the fruit, the basket length is 5 and everything seems great. Later in the code when I try to add properties to a single piece of fruit...
basket[2].status='rotten';
...every banana is then rotten and not just the 3rd one. The apples are all the same as before with an undefined status.
I am very new to Javascript, so I apologize if this is something that is normally common knowledge. Everything I have Googled hasn't been what I was looking for and trying to learn from what I did find hasn't helped.
You are copying the reference to the fruit[i] in basket and not the cloning it. You have to clone it using something like this
var fruitToBePushed = JSON.parse(JSON.stringify(fruit[i]));
Example Fiddle
Complete Code:
var fruit = [
{ type:"banana", copies:"3" },
{ type:"apple", copies:"2" },
];
var basket=[];
for (var i=0; i < fruit.length; i++){
for(var x=0; x < fruit[i].copies; x++){
var fruitToBePushed = JSON.parse(JSON.stringify(fruit[i]));
basket.push(fruitToBePushed);}}
basket[2].status='rotten';
console.log(basket);
There is a simple way to achieve your goal: Array.prototype.slice()
var a=[1,2,3];
var b=a;
var c=a.slice();
a[1]=99;
Here is a Fiddle to play with.
Offtopic:
Since you are a novice to JS, I would recommend to have a look at Array.map(), Array.filter() and Array.reduce(). This will help you, writing more concise code.
This is also referred to as "deep copying", which I've found some articles on. Closest seems to be this one but it's for jQuery - I'm trying to do this without a library.
I've also seen, in two places, that it's possible to do something like:
arr2 = JSON.decode(JSON.encode(arr1));
But that's apparently inefficient. It's also possible to loop and copy each value individually, and recurs through all the arrays. That seems tiring and inefficient as well.
So what's the most efficient, non-library way to copy a JavaScript multi-dimensional array [[a],[b],[c]]? I am completely happy with a "non-IE" method if necessary.
Thanks!
Since it sounds like you're dealing with an Array of Arrays to some unknown level of depth, but you only need to deal with them at one level deep at any given time, then it's going to be simple and fast to use .slice().
var newArray = [];
for (var i = 0; i < currentArray.length; i++)
newArray[i] = currentArray[i].slice();
Or using .map() instead of the for loop:
var newArray = currentArray.map(function(arr) {
return arr.slice();
});
So this iterates the current Array, and builds a new Array of shallow copies of the nested Arrays. Then when you go to the next level of depth, you'd do the same thing.
Of course if there's a mixture of Arrays and other data, you'll want to test what it is before you slice.
I'm not sure how much better JSON.stringify and JSON.parse than encode and decode, but you could try:
JSON.parse(JSON.stringify(array));
Something else I found (although I'd modify it a little):
http://www.xenoveritas.org/blog/xeno/the-correct-way-to-clone-javascript-arrays
function deepCopy(obj) {
if (typeof obj == 'object') {
if (isArray(obj)) {
var l = obj.length;
var r = new Array(l);
for (var i = 0; i < l; i++) {
r[i] = deepCopy(obj[i]);
}
return r;
} else {
var r = {};
r.prototype = obj.prototype;
for (var k in obj) {
r[k] = deepCopy(obj[k]);
}
return r;
}
}
return obj;
}
As you asked for performance, I guess you also would go with a non-generic solution. To copy a multi-dimensional array with a known number of levels, you should go with the easiest solution, some nested for-loops. For your two-dimensional array, it simply would look like this:
var len = arr.length,
copy = new Array(len); // boost in Safari
for (var i=0; i<len; ++i)
copy[i] = arr[i].slice(0);
To extend to higher-dimensional arrays, either use recursion or nested for loops!
The native slice method is more efficient than a custom for loop, yet it does not create deep copies, so we can use it only at the lowest level.
Any recursive algorithm that doesn't visit the same node twice will be about as efficient as you get with javascript (at least in a browser) - in certain situations in other languages you might get away with copying chucks of memory, but javascript obviously doesn't have that ability.
I'd suggest finding someone who's already done it and using their implementation to make sure you get it right - it only needs to be defined once.
I know that in JavaScript sometimes the system creates a fake array, meaning it is actually an object and not an instance of Array, but still has part of the functionality of an array. For example, the arguments variable you get inside functions is a fake array created by the system. In this case I know that to turn it into a real array you can do:
var realArray = Array.prototype.slice.call(fakeArray);
But what if the fake array wasn't created by the system, what if fakeArray was simply:
var fakeArray = { "0": "some value", "1": "another value" };
In this case, and I tested it, using the method above will result in an empty array. The thing I want to be able to turn a fake array like in the example I gave (created by me and not by the system) into a real array. And before you tell me to simply make the fake array a real array from the beginning, you should know that I get the fake array from a resource which I have no control of.
So, how do I turn a fake array not created by the system into a real array?
Your example will work if your "fake array" is given a .length property appropriately set.
This will not work in some older versions of Internet Explorer.
"one of the purposes of turning the fake array into a real array is to get its length"
If you want the number of properties in the object, use Object.keys...
var len = Object.keys(fakeArray).length;
To shim Object.keys for older browsers, you can do this...
if (!Object.keys) {
Object.keys = function(o) {
var keys = [];
for (var k in o)
if (o.hasOwnProperty(k))
keys.push(k)
return keys;
};
}
If the fake Array is "sparse", you'll need a solution like #Rocket shows.
You can just simply loop through the "array" and save the values into a real array.
var fakeArray = { "0": "some value", "1": "another value" };
var realArray = [];
for(var i in fakeArray){
realArray[i] = fakeArray[i];
}
You can iterate the object properties, while pushing the ones you want to your new array:
var array = [];
for (var i in fakeArray) if (fakeArray.hasOwnProperty(i)) {
array.push(fakeArray[i]);
}