JavaScript - identify a property within an array of objects as an array - javascript

I appreciate there are a few questions here already about how to identify an array. However, I can't find anything regarding identifying an array within an array of objects.
Given the following array:
var pagesArray = [{
category: 'pages1',
pages: [
'firstPage1',
'secondPage1',
'thirdPage1',
'fourthPage1']
}
}];
I have tried to loop through and identify that pages is also an array like so:
EDIT
Actually o in the next example should have been pagesArray but I've left it as is so that the answers make sense.
for(var p in o){
console.log(typeof p);
console.log(p instanceof Array)
console.log(Object.prototype.toString.call(p))
}
The output returned for pages is:
string
false
[object String]
Is there a way to correctly identify that the property is an array or am I misunderstanding something?

For this answer, I assume your are using a for..in loop to iterate over the properties of the object inside pagesArray, that is, pagesArray[0].
for..in iterates over keys, not values. Keys are always strings, so in your loop, p is always a string (here, "categories", or "values"). To get the value associated with a key, use o[p]. You want to test if o["pages"] is an array, not if the string "pages" is an array.
for(var p in o){
var value = o[p];
console.log(typeof value);
console.log(value instanceof Array)
console.log(Object.prototype.toString.call(value))
}

pagesArray[0].pages instanceof Array works fine, though you have an extra curly brace causing a syntax error.
http://jsfiddle.net/vVPCA/

Related

forEach loop quirky behavior with undefined values?

Was writing a script in JS to make some dummy data for testing my API and ran into an interesting quirk with the forEach loop in JS.
const dictionary = {};
const undefinedArray = Array(3); // [undefined, undefined, undefined]
undefinedArray.forEach((_, index) => {
console.log('Logging at index: ', index)
const someObject = { id: index };
if (!dictionary[someObject.id]) {
dictionary[someObject.id] = someObject
}
});
console.log(dictionary);
After checking the output of this snippet, you'll see that nothing inside the forEach loop is logged and the dictionary is still an empty object. I was talking with my coworker about this behaviour and he said he ran into this particular issue before and offered this as a solution.
const dictionary = {};
const undefinedArray = [...Array(3)]; // [undefined, undefined, undefined]
undefinedArray.forEach((_, index) => {
console.log('Logging at index: ', index)
const someObject = { id: index };
if (!dictionary[someObject.id]) {
dictionary[someObject.id] = someObject
}
});
console.log(dictionary);
By wrapping the Array constructor in square brackets and utilizing the spread operator, now the array is looped through and the object is built correctly. This fascinated me, so I went to the documentation for the Array object and found this:
arrayLength
If the only argument passed to the Array constructor is an integer between 0 and 2^32 - 1 (inclusive), this returns a new JavaScript array with its length property set to that number (Note: this implies an array of arrayLength empty slots, not slots with actual undefined values). If the argument is any other number, a RangeError exception is thrown.
So apparently it is not assigning each value undefined, but only setting its length property to whatever is passed in the constructor. This is not apparent when you log Array(n) to the console because it shows an array with n undefined values. I assume the toString method for the Array object is based on its length property and uses a normal for or for of loop to construct the string.
It does begin to make a little bit more sense, however, when you explicitly set an index of the newly defined array. In the snippet below, the same array is initialized, but the zero index is explicitly assigned undefined as a value. Since this is an "actual undefined value" in Mozilla's words, the forEach loop exectues at index zero.
const dictionary = {};
const undefinedArray = Array(3); // [undefined, undefined, undefined]
undefinedArray[0] = undefined
undefinedArray.forEach((_, index) => {
console.log('Logging at index: ', index)
const someObject = { id: index };
if (!dictionary[someObject.id]) {
dictionary[someObject.id] = someObject
}
});
console.log(dictionary);
Array.map() behaves the same way. So I guess my main question would be, are there other ways to execute forEach and map without filling the array or by using the quirky hack I mentioned earlier?
To recap: these are the two work arounds I've found for this particular use case:
[...Array(n)] OR Array(n).fill(). Both of these mutations to the array will allow a forEach loop to iterate over all values in the array.
So apparently it is not assigning each value undefined, but only setting its length property to whatever is passed in the constructor.
Correct. (Provided you pass only a single argument and it's a number. If you pass a non-number, or pass more than one argument, they're used as elements for the array. So Array("3") results in ["3"]; Array(3, 4) results in [3, 4].)
This is not apparent when you log Array(n) to the console because it shows an array with n undefined values.
It depends on what console you use. The devtools in Chromium browsers show (3) [empty x 3] for exactly that reason, to differentiate between empty array slots and ones containing the value undefined.
So I guess my main question would be, are there other ways to execute forEach and map without filling the array or by using the quirky hack I mentioned earlier?
If you want forEach and map to visit elements of the array, they have to actually exist. Those methods (and several others) are defined such that they don't call your callback for empty slots in sparse arrays. If by "quirky hack" you mean [...Array(3)], that's also filling the array (and is fully-specified behavior: [...x] uses the iterator x provides, and the array iterator is defined that it yields undefined for empty slots rather than skipping them as forEach, map, and similar do). Doing that (spreading the sparse array) is one way to create an array filled with undefined (not empty) elements. Array.fill is another. Here's a third: Array.from({length: 3})
const a = Array.from({length: 3});
a.forEach(value => {
console.log(`value = ${value}`);
});
Which you use is up to you. Array.from is very simple and direct. Similarly Array(3).fill(). I probably wouldn't use the spread version (just because I think it's fairly unclear to people who don't have a deep knowledge of how the array iterator works), but it's a matter of style.

JavaScript "Associative" Object vs. Array

at the moment I am using an object to simulate an associatve array.
The object acts like a one to many relationship table from mysql
For Example:
var obj = {
105: [],
200: []
//...
}
My property names are only numeric so I found out that I could use an array, too.
But then the empty entries between the indices are filled with undefined.
var arr = [];
arr[10] = "Value";
arr[15] = "Value 2";
//arr => [undefined*10, "Value", undefined*4, "Value 2"]
So when I am going to iterate over that array I have to check if the value at the current index is set.
So the question is, which of the solutions is faster or better.
Using an object or an array where
the empty space between the indizes is filled with undefined values.
If you need a key-value association, you need an object.
If you want an ordered list in which the keys don't really matter, an array it is.
FYI, the indices "between" set indices in an array are not actually "filled" with undefined; you merely get the undefined value when you try to access a non-existing property on any object:
({}).foo; // undefined
You are asking which is "faster or better". For "better" many possible answers exist, but "faster" has a clearer answer: as others pointed out there is hardly any speed difference between Object and Array. Both have the same roots in the prototype hierarchy. An Array's empty values are not "filled".
Regarding your use case the bottleneck will not be the object, but how you loop it! This actually does make a difference: for loops are faster than any other way of looping. But you have the problem of the non-continuos numeric indices.
Therefore: just benchmark how long it takes if you do
for(key in object)
keys = Object.keys() and for(var i = 0; i < keys.length; i++)
as these two should be your fastest options.

Delete object properties similiar like deleting array elements with splice?

We have object type variable in Jquery:
var obj = *{"1234" : "xx", "4321" : "yy", "5555" : "hh", "2321" : "aa" };*
Lets say that I want to delete every property from property name "5555" to the end of the object(that means that I want to delete obj['5555'] and delete obj['2321'] ).
I am interested in smartest way, trough loop, to do that.
In array I would use splice(2, arr.length) but I am confused.
There's no guarantee as to the order of an object's properties. When pasting your example object in my console, Here's what I saw:
> obj = {"1234" : "xx", "4321" : "yy", "5555" : "hh", "2321" : "aa" }
Object {1234: "xx", 2321: "aa", 4321: "yy", 5555: "hh"}
As you can see, chrome ordered the properties in ascending order, like it would an array. Who knows, but maybe IE doesn't do this... Maybe some obscure browser would order the properties in descending order... There's no way of knowing what the actual object will look like, so perhaps have a rethink.
If all your properties are, essentially numeric, there's nothing wrong with using an array, in JS, the Array prototype is nothing but an augmented Object:
obj = [];
obj[1234] = 'xx';
obj[2321] = 'aa';
obj[5555] = 'hh';
The numeric indexes are coerced to strings internally anyway (because Array's are objects), so JS isn't going to create endless empty indexes for you, it resolves obj[200] just like it would resolve objectLiteral.undefinedProperty: scan instance, then prototypechain. If the requested property wasn't found, return undefined.
They're added as you go along, yet here are 2 ways:
//Assume simpel object (non-nested)
var newObj = JSON.parse(JSON.stringify(obj).replace(/\{|,\s*"5{4}"\s*:.+$/,'}'));
This, I think, is the easiest way, but not very reliable. The most "tried and tested" apprach is:
var unset = false;
for (var prop in obj)
{
if (obj.hasOwnProperty(prop))
{
unset = unset || !!(prop === '5555');
if (unset === true)
{
delete(obj[prop]);
}
}
}
The last approach creates an array, containing all keys, which you can iterate over, and delete the properties that way. It's here for completeness' sake only: I wouldn't use it, though, simply because it requires a browsers that supports the ES5 spec (not all browsers do, sadly), and there's no real advantage over the code above:
var keys = Object.keys(obj).sort(),//call sort to assure ascending order
toDelete = keys.splice(keys.indexOf('5555'), keys.length).sort();
for(var i=0;i<toDelete.length;i++)
{
delete obj[toDelete[i]];
}
Push the obj to be searched and the value to be found into a function that returns a new object with only those properties up to the value you specified.
function returnNewobj(obj, value) {
var newObj = {};
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
if (prop === value) return newObj;
newObj[prop] = obj[prop];
}
}
return newObj;
}
Edit: probably not necessary, but I added the hasOwnProperty line to be on the safe side.
Edit2: It's worth pointing out that new properties are added to objects in alphanumerical order, not to the end of objects like elements are added to arrays. So don't get caught out by that.
I would recommend you to get the index of the element from which you want to start deleting elements and then looping throw the object deleting the elements with a higher index.
You might want to create a function to make it more similar to splice i you prefer.
Find the element you are looking for and set that particular element and any element after that to null.
Its very tricky to delete property name however property value can be made null or undefined but removing property is difficult but you can do one thing can copy the required properties in new object its a workaround though here is a sample fiddle i use this often in this kind of situation
objCopyTo[strToPropertyName] = objCopyFrom[strPropertyName];
here is the fiddle

Issue Reversing Array of Objects with JS

I'm parsing JSON and getting an array of objects with javascript. I've been doing this to then append an element for each object:
for(o in obj){ ... }
But I realized that for a certain situation I want to go backwards through the array. So I tried this before the for loop:
obj = obj.reverse();
However this isn't reversing the order of the objects in the array. I could simply put a count variable in the for loop to manually get the reverse, but I'm puzzled as to why reverse doesn't seem to work with object arrays.
There's no such thing as an "object array" in JavaScript. There are Objects, and there are Arrays (which, of course, are also Objects). Objects have properties and the properties are not ordered in any defined way.
In other words, if you've got:
var obj = { a: 1, b: 2, c: 3 };
there's no guarantee that a for ... in loop will visit the properties in the order "a", "b", "c".
Now, if you've got an array of objects like:
var arr = [ { a: 1 }, { b: 2 }, { c: 3 } ];
then that's an ordinary array, and you can reverse it. The .reverse() method mutates the array, so you don't re-assign it. If you do have an array of objects (or a real array of any sort of values), then you should not use for ... in to iterate through it. Use a numeric index.
edit — it's pointed out in a helpful comment that .reverse() does return a reference to the array, so reassigning won't hurt anything.
That's because the for (o in obj) doesn't iterate the array as an array, but as an object. It iterates the properties in the object, which also includes the members in the array, but they are iterated in order of name, not the order that you placed them in the array.
Besides, you are using the reverse method wrong. It reverses the array in place, so don't use the return value:
obj.reverse();

what happens when javascript array is treated like an object?

the index variable below is incorrectly initialized because f() will be returning stuff other than numbers, like strings. So what's the worst that can happen here? My testing seems to indicate that it has no effect, but now I am wondering...
function index(o, f) {
var index = []; // should be index = {};
each(o, function(k, v, o) { index[f(k, v, o)] = v; });
return index;
}
Javascript arrays are special objects that have an automatically set length property and inherit Array.prototype.
Unless you use a length property, there is no harm in treating an array as an object.
An array is an object, thus it can be treated as such without much side effects. Doing so however might result in some confusion, as the length property does not count non-numeric keys, and all the array prototype functions will likewise ignore them.
Just change [] to {}
You'll be creating an associative array, which is a valid JavaScript structure. Although, it is technically different than an object, you can interact with the array just like you would an object (for ... in to iterate, myarray[key] to fetch values). You may want to consider returning an object instead of an array if you suspect some keys will be strings.
"f() will be returning stuff other than numbers, like strings"
If f() only returns strings, then you're good to go, you're just using your array as an object and adding properties. The only downside is that the array itself remains empty, so for example you cannot count how many items you have added.
If f() can return both strings and numbers, it's going to create a mess. The loop will populate sometimes the array, sometimes the object properties.
I am not sure what you mean by "like strings", but if what f() returns is neither a number nor a string then it's not going to work.

Categories