What values do the <empty> elements created by new Array() have? - javascript

I'm quite confused by these little guys. After I encountered some funny behavior between them and Array.prototype.filter I fooled around in re.pl trying to understand their true value. But it seems like they switch from <empty> to undefined depending on who's looking (at least in re.pl and node, they're logged as undefined in this environment).
let emptyArr = new Array(5);
//set up two control elements
emptyArr[0] = 0;
emptyArr[4] = undefined;
console.log('\nemptyArr:', emptyArr)
console.log('\npeeking at an empty element:', emptyArr[1])
console.log('\nfilter for undefined elements:', emptyArr.filter(e => e === undefined))
console.log('\nfilter for any element:',
emptyArr.filter(e => {
console.log("ele:", e)
return true
})
) // only two elements are registered here
console.log('\nmappedEmpty:', emptyArr.map(e => e)) //everything is preserved
console.log('\ngenerated array', Array.from(emptyArr))
console.log('\nalways true filter on generated array:', Array.from(emptyArr).filter(e => true)) // empties are now 'true' undefined
What's the story here? Quirky array prototype methods or a secret ultra-false-y value?

What's the story here? Quirky array prototype methods or a secret ultra-false-y value?
Arrays are objects. Elements of the array are simply properties of the underlying object. Accessing a property that doesn't exist returns undefined. Therefore when you access emptyArr[1] you get undefined. Looking at the console of Chrome might help:
As you can see, 0 and 4 exist because you created those entries by assigning to them. 1, 2 and 3 don't exist.
These positions with no value are often referred to as "holes". Your array has holes at positions 1, 2 and 3. An array with holes is also called "sparse array".
Most array methods (.filter, .map, etc) skip over holes. We can easily prove this for some methods:
// Array#map
console.log([,,,42].map(() => 21)); // [,,,21], not [21,21,21,21]
// Array#every
console.log([,,,42].every(x => x === 42)); // true, not false
Of course we could also just look at the language specification, where it says for Array#every for example:
callbackfn is called only for elements of the array which actually exist; it is not called for missing elements of the array.
Array.from on the other hand explicitly looks at the .length property of the value passed to it and it will copy any property/element between 0 and .length. In other words, it does not skip holes.
Look at the difference between the arrays in the Chrome console:
Worth noting maybe that arr.length doesn't care about holes. It will always be the highest set index in the array + 1.

Well actually they do not exist. Imagine arrays being objects, using new Array(5) you will get:
{
length:5
}
So like objects they return undefined for nonset values:
array[3] // undefined
But that doesnt mean theres a property set to undefined. That changes when you use Array.from . You can imagine it doing sth like
for(var i = 0; i < oldarray.lemgth; i++){
newarray[i] = oldarray[i];
}
So after the Array.from call itll look like this:
{
length:5,
0:undefined,
1:undefined,
2:undefined,
3:undefined,
4:undefined
}

They are undefined. It just depends on the engine how it displays them, but trying to access them returns undefined, because that's what they are - elements that have not been defined.
For example the Chrome console will print > (5) [empty × 5] while node prints [ , , , , ] for the same new Array(5). It's just a more visual representation than, say, showing [undefined, undefined, undefined, undefined, undefined].

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.

Empty slots in JavaScript objects?

Lately I started seeing this in firefox' console
Object [ <6 empty slots>, false, <3 empty slots>, 1 more… ]
When I have an object like
{
6: false,
10: true
}
I simply want an object with numeric keys that I can access, but I am worried by this because if it keeps track of empty slots then this must mean that some memory is wasted?
Are my concerns valid and if yes what would be a correct way to define such an object?
The problem might be caused at how Firefox' console.log has interpreted the input object. Somehow, it got evaluated as an array instead of a simple object. Chrome does it right. If you look deeper into how an array is managed in Javascript, you can find the following:
Arrays cannot use strings as element indexes (as in an associative array), but must use integers. Setting or accessing via non-integers using bracket notation (or dot notation) will not set or retrieve an element from the array list itself, but will set or access a variable associated with that array's object property collection. The array's object properties and list of array elements are separate, and the array's traversal and mutation operations cannot be applied to these named properties. src
A better comprehending for this is to tinker with Array's length property. Especially when you have constructed your array by using []. To add elements to the array, we have to use .push(...). This function uses the length property (check 15.4.4.7 Array.prototype.push). So in short (interactive example is at the bottom)
const arr = []; // length = 0
arr.push('1stEl', '2ndEl', '3thEl'); // length = 3
// this isn't allowed, but you can do this
arr[7] = '7thEl'; // length = 8
You see that the length is now 8 and not 4. The indices 3..6 are reserved, but undefined. Here below is a console output.
[
"1stEl",
"2ndEl",
"3thEl",
undefined,
undefined,
undefined,
undefined,
"7thEl"
]
If you use a .push method again, it will place the new element after the '7thEl' element (so on index 8).
To check the keys that is used by this object, we can use Object.keys() on the array. You will get
[
"0",
"1",
"2",
"7"
]
You see that numeric values are used as keys. Like your object, which is
{
6: false,
10: true
}
Using Object.keys on this object gives ["6", "10"]. It has a similar output as the above. So the console.log from firefox has interpret your object as an array, thus displaying it as an array. In order to display the array correctly, it starts (logically seen, need to check the source code yet) at key 0 and ends at key array.length - 1. But the indexes 0,1..5 and 7..9 aren't "defined". Thus it leads to this output
Object [ <6 empty slots>, false, <3 empty slots>, 1 more… ]
I'm not sure if I have to qualify this as a bug or glitch at Firefox's console API... Or that the console input (when initializing a variable) has read the object incorrectly.
--- live example --
const a = new Array(3);
console.log('using "new Array(...)" reserves memory space: ' + a.length);
console.log('---');
// using brackets
const b = [];
console.log('but what with [] ? At initial, we have ' + b.length);
b.push('1stEl', '2ndEl', '3thEl');
console.log('After push(\'1stEl\', \'2ndEl\', \'3thEl\'), we have ' + b.length);
// add to random index
b[7] = '7thEl';
console.log('After b[7] = \'7thEl\', we have ' + b.length);
console.log('displaying gives ', b);
console.log('using Object.keys: ', Object.keys(b));
// adding again
b.push('newEl');
console.log('After b.push(\'newEl\'), we have ' + b.length);
// object
const obj = {
6: false,
10: true
};
console.log('obj defined as {6: false, 10: true }');
console.log('using Object.keys: ', Object.keys(obj));
console.log('obj: ', obj);
Javascript uses sparse arrays. "Since an array's length can change at any time, and data can be stored at non-contiguous locations in the array, JavaScript arrays are not guaranteed to be dense; this depends on how the programmer chooses to use them." (source)
If the objects are of type Array, then the memory used is an implementation detail of the engine. In your case, the objects are objects, so it only takes the memory for the object itself, and to store the property names and references to property values.

How does this Array.apply method works?

I understand you can pass an array to Function.prototype.apply but recently I've come across this code (it was written as a good way to create an array with undefined values as opposed to empty slots or holes);
var a = Array.apply( null, { length: 3 } );
I can't understand how this code works starting with the second argument. Is there another syntax that could be used to make it more understandable what's going on? What's going on when we pass an actual object to apply as opposed to an array? Can we somehow translate this object to an array to get this result? I've tried many things to do this, but without success.
Oh, that is just horrible.
So, when you call Array as a function, it doesn't really care what this is so you can tell it that this is null.
The second argument for apply is an array-like object which gives the list of arguments.
If you had [0, 0, 0] then that would be like this object:
{
0: 0,
1: 0,
2: 0,
length: 3
}
… because arrays get a length property which equals the value of their highest index. A real array would get other properties from the prototype, but we (and apply, and Array) don't care about them.
The arguments you pass to Array become its initial values.
Now, it seems that (internally) Array checks the length of its arguments and then sets its internal values using something like:
for (var i = 0; i < arguments.length; i++) {
internal_array_values.push(arguments[0])
}
And if you have an object which consists solely of { length: 3 } it is going to get undefined as the value of each of those three arguments giving you [undefined, undefined, undefined].
http://www.2ality.com/2012/07/apply-tricks.html
With apply and Array (which can be used as either a function or a constructor), you can turn holes into undefined elements:
Array.apply(null, ["a",,"b"])
// [ 'a', undefined, 'b' ]

Generating a filled array in javascript [duplicate]

This question already has answers here:
Undefined values in Array(len) initializer
(5 answers)
Closed 7 years ago.
I would expect the following code to return [1,1,1,1...]
(new Array(10)).map(function() { return 1;})
but it returns [, , , , , ...].
Moreover,
(new Array(10)).length == 10 and (new Array(10))[0] == undefined are true.
And for z = function(){return 0;}; the expression z(undefined) === 0 is also true.
Yet I have noticed that [,,,,,,,,,,].map(function() { return 1; }) also returns [,,,,....].
Can anyone explain why?
So. I would expect the following code to return [1,1,1,1...].
(new Array(10)).map(function() { return 1;})
But it returns [, , , , , ...].
Right, because new Array(10) creates an array with no elements with a length of 10, and map only iterates over elements that actually exist. (And yes, this is surprising. :-) )
Moreover, (new Array(10)).length == 10 and (new Array(10))[0] == undefined are true.
Again correct, because (again) new Array(10) doesn't put any elements in the array, so accessing [0] gives you undefined.
JavaScript's standard arrays aren't really arrays at all, and they can have a length that's a positive number without having any entries in them. They're a form of "sparse" array.
Let's take a simpler example:
var a = new Array(10);
a[2] = 1;
That array contains one element, the element at index 2. There is no element at index 0, no element at index 1, and no elements at indexes 3 and above. It just has gaps there. You can tell by asking it:
console.log(0 in a); // "false"
console.log(1 in a); // "false"
console.log(2 in a); // "true"
Standard arrays in JavaScript are just objects with special behavior assigned to length, special behavior assigned to a class of property names (loosely, numeric ones), and that are backed by Array.prototype.
This is all in contrast to the newer "typed" arrays, Int32Array and such, which are true arrays in the traditional sense.
This will work:
Array.apply(null, Array(10)).map(…
Live demo: http://jsfiddle.net/xXG5p/
You have to make the array dense first. new Array(n) creates a sparse array. Sparse arrays do have a length but they have no elements. Hence, a .map() call will not iterate at all.
Using my code above, you will create a dense array that does contain elements (whose values are initially set to null).
Here is what the Mozilla Developer Network says about Array.prototype.map:
callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values.
When you call new Array(10), the Array you create believes it is 10-element long but none of its indexes have ever been assigned to. Here's an example of what happens if you use a literal array for which you have not set values for some indices:
[1, , 3].map(function () {return "a"})
The value you get is:
[ 'a', , 'a' ]
Because at index 1, no value was assigned.

Why does delete keep Array elements?

Today I stumbled upon a question here on Stack Overflow - How do I remove objects from a javascript associative array?. What struck me was that the accepted answer was both misleading and heavily upvoted, so I highlighted the possible pitfall.
However, while cobbling together a corrective answer, I realized I have no idea as to why it makes sense for delete to keep elements assign undefined instead of removal.
var elements = new Array()
elements.push(NaN)
elements.push(NaN)
elements.push(NaN)
delete elements[1]
console.log("number of elements: ", elements.length) // returns 3
Is there a rationale behind it?
I realized I have no idea as to why it makes sense for delete to assign undefined instead of removal.
It doesn't. delete removes properties from objects, it does not set them to undefined. Here's an easy way to tell:
var a = ['a', 'b', 'c'];
console.log(1 in a); // logs "true"
delete a[1];
console.log(1 in a); // logs "false"
Note that after the delete, a doesn't have a property called 1 anymore. At all.
Contrast with:
var a = ['a', 'b', 'c'];
console.log(1 in a); // logs "true"
a[1] = undefined;
console.log(1 in a); // logs "true"
There, a still has a property called 1, it's just that the property's value is undefined.
It's useful to understand that in JavaScript, arrays aren't really arrays at all. They're just objects, array "indexes" are just property names (which are strings — yes, really, we just tend to write them as numbers), arrays have special handling of property names that are all numeric (indexes), a special length property, and some functions they get from Array.prototype. This is very clearly laid out in Section 15.4 of the spec. Once you have it set firmly in your head that JavaScript arrays aren't really arrays, they make a lot more sense. :-)
Deleting an array "index" property from an array does not change its length (not even if you delete the highest-numbered one); it just creates a hole in the array (JavaScript "arrays" are sparse arrays by their nature; e.g., they can have gaps in them). So in my first example above, I get exactly the same array that I'd've gotten if I'd done this:
var a = [];
a[0] = 'a';
a[2] = 'c';
Note the gap, the array has no 1 element/property.
If you say:
var foo = a[3];
...foo can get the value undefined for two completely different reasons:
a has a property called 3 that has the value undefined, or:
a has no property called 3 at all; the result of a property accessor operation on an object that doesn't have a property by that name is undefined. This if-no-property-return-undefined is covered by the spec in a fairly convoluted way, but mostly in Section 8.12.3.
These are very distinct things.
It doesn't assign undefined. It deletes the property. (If you try to access a property that doesn't exist, you will get undefined, and length is based on the highest numbered item in the array).
It makes sense, because it works that way on any kind of object. For it to act otherwise, it would have to special case objects if they were an instanceof Array but only if it was a property with a numeric name.
Use splice if you want to remove an item from an array.
> var elements = [NaN, NaN, NaN];
> elements.splice(1,1);
> console.log(elements);
[ NaN, NaN ]
JavaScript arrays can be "sparse". That is, some slots can be empty in the sense of never having had a value assigned, or having a value deleted. If you test the value associated with that index you'll get back undefined because it doesn't exist, not because it was assigned the value undefined.
When delete removes an item from an array it doesn't automatically slide the rest of the elements up to fill the space: the other elements retain their existing indexes, which in turn means the .length property doesn't change since .length is defined as being equal to the highest assigned index plus one.
If you want to remove an array element and have the other elements renumbered use the .splice() method.
This is because delete operator removes property, and removed property has value undefined. To remove element from array you can use splice method of array.
All of this is because of how the delete operator in javascript work.
When you do not set property of object and try to check its value it will be undefined:
var obj = {};
alert(obj.foo); // undefined
which is the same as:
alert(obj['foo']); // undefined
And look at this:
// create empty object
var obj = {};
// check its property named 1
alert(obj[1]); // undefined
// set property named 1 to value 'fooo'
obj[1] = 'fooo';
// and check it
alert(obj[1]); // 'fooo'
// now delete it
delete obj[1];
// and check again
alert(obj[1]); // undefined
delete has removed property and its value is undefined - and all above code was about object.
Now look at the arrays:
var arr = []; // it's better to use [] than new Array();
alert(arr[1]); // undefined - same as above
// assign value
arr[1] = 'fooo'
// check it
alert(arr[1]); // 'fooo' - same as above
// remove it
delete arr[0];
// and check
alert(arr[1]); // undefined - same as above
So behavior is the same, but what about length property of array. Specification http://es5.github.com/#x15.4.5.2 says:
"The length property of this Array object is a data property whose value is always numerically greater than the name of every deletable property whose name is an array index."
So when you look at this:
var arr = ['foo', 'bar', 'foobar'];
alert(arr.length); // 3
// delete first
delete arr[1];
// and check length
alert(arr.length); // 3
Last check gives 3 because the last deletable property in this array has index 2 - first property (with index 0) has value undefined (delete operator set this value), second item (with index 1) has value 'bar' and third (with index 2) has value 'foobar'. So according to specification length = 2 + 1 ('allways numerically greater than last deletable');
This is visible also in this code:
var arr = [];
arr[10] = 'foo';
// element with index 10 is set to 'foo' but elements from 0 to 9 don't have value - they are undefined
// now check length
alert(arr.lenght); // 11
Last deletable property index is 10, so 10 + 1 gives 11, despite of previous elements that are undefined.
So delete operator does his work, but it is not designed to remove items from array.

Categories