Sorting properties in Javascript is broken - javascript

I need to loop through a JavaScript object treating it as an array with custom keys.
I know this is not fully supported, since properties have no instrinsic order, but since I always reorder the properties, I found this approach simple and reliable... until now.
The problem occurs when the keys are numbers, or strings that can be casted as numbers.
When I run this code:
var test1 = {4294966222:"A",4294966333:"A",4294966111:"A"};
var test2 = {4294968222:"A",4294968333:"A",4294968111:"A"};
for (var k in test1) {console.log(k);}
console.log("---");
for (var k in test2) {console.log(k);}
the output is:
4294966111
4294966222
4294966333
---
4294968222
4294968333
4294968111
Which means:
(test1) if the keys are below 2^32 (4,294,967,296), they are automatically reordered, the smallest first
(test2) if the keys are above 2^32, they are NOT reordered.
The question is: why is this happening?
Since all the browsers I tested (Google Chrome 79.0, Mozilla Firefox 71.0, Microsoft Edge 44.18362, Internet Explorer 11.535) agree about this output, there must be some official specification.
Update
I tested a lot of numbers before finding out it was a threshold matter. I found odd that the sequence 2,3,1 behaves differently from three timestamps ordered in the same way.

This is expected. Per the specification, the method that iterates over properties, OrdinaryOwnPropertyKeys, does:
For each own property key P of O that is an array index, in ascending numeric index order, do
a. Add P as the last element of keys.
For each own property key P of O that is a String but is not an array index, in ascending chronological order of property creation, do
a. Add P as the last element of keys.
The ascending numeric order only applies for properties which are array indicies.
So, what is an "array index"? Look it up::
An integer index is a String-valued property key that is a canonical numeric String (see 7.1.21) and whose numeric value is either +0 or a positive integer ≤ 2^53 - 1. An array index is an integer index whose numeric value i is in the range +0 ≤ i < 2^32 - 1.
So, numeric properties that are greater than 2^32 are not array indicies, and therefore iterated in order of property creation. However, numeric properties that are less than 2^32 are array indicies, and are iterated over in ascending numeric order.
So, for example:
1: Array index, will be iterated over numerically
10: Array index, will be iterated over numerically
4294968111: Greater than 2 ** 32, will be iterated over after array indicies are finished, in property creation order
9999999999999: Greater than 2 ** 32, will be iterated over after array indicies are finished, in property creation order
Also, keep in mind that, contrary to popular belief, property iteration order is guaranteed by the specification as well, thanks to the for-in iteration proposal which is stage 4.

This has to do with the way they keys of an object are traversed.
According to the ES6 specifications it should be:
9.1.12 [[OwnPropertyKeys]] ( )
When the [[OwnPropertyKeys]] internal method of O is called the following steps are taken:
Let keys be a new empty List.
For each own property key P of O that is an integer index, in ascending numeric index order
Add P as the last element of keys.
For each own property key P of O that is a String but is not an integer index, in property creation order
Add P as the last element of keys.
For each own property key P of O that is a Symbol, in property creation order
Add P as the last element of keys.
Return keys.
http://www.ecma-international.org/ecma-262/6.0/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys
That means if the value of a key stays the same if converted to an unsigned 53 bit number
and back it is treated as an integer index which gets sorted in ascending numeric order.
If this fails it's treated as a string key, which are ordered the way they were added
to the object.
The catch here is that all major browsers don't follow this specification yet
and use an array index instead which is limited to a positive number up to .
So anything above that limit is a string key actually.

Related

How is the workflow/ sequence of the JS: compare function to an array?

I know that's function:
function myFunction() {
points.sort(function(a, b){return a - b});
is fully working for sorting array contain number in ascending..
If the result is negative a is sorted before b.
If the result is positive b is sorted before a.
If the result is 0 no changes are done with the sort order of the two values.
But how's the sequence for the compare "a" and "b" ?
Is it "index0" compare "index1", then "index1" compare "index2", etc..?
I just don't understand this "compare function" workflow/ sequence...
It is unspecified. The specification says:
Perform an implementation-dependent sequence of calls to the Get, Set, DeletePropertyOrThrow, and HasOwnProperty abstract operation with obj as the first argument, and to SortCompare (described below), such that:
...
Because this order is unspecified, we used to be able to see behaviors like this, in which an inconsistent sort algorithm results in different sorting results in different environments, because the different environments were comparing array items in different orders.
If you do have a stable, consistent sorting algorithm, then the order in which items are compared should not matter, especially given that sorting is now supposed to be stable as of ES2019 ("that is, elements that compare equal must remain in their original order").
No matter the order in which the engine compares items, the resulting array should be the same across environments. It may compare item [0] against item [1]first, or it may compare item [length - 1] against item [length - 2] first.

Array length is not correct in javascript

I have an array like below
arr=[];
arr[0]={"zero": "apple"};
arr[1]={"one": "orange"};
arr["fancy"]="what?";
but i am getting length as 2 when i do console.log(arr.length) even though i am able to console all the values .
and not able to get all values while doing console.log(JSON.stringify(arr))
What is the issue here.
here is the link to fiddle fiddle
.length is a special property in Javascript arrays, which is defined as "the biggest numeric index in the array plus one" (or 2^32-1, whatever comes first). It's not "the number of elements", as the name might suggest.
When you iterate an array, either directly with for..of or map, or indirectly with e.g. JSON.stringify, JS just loops over all numbers from 0 to length - 1, and, if there's a property under this number, outputs/returns it. It doesn't look into other properties.
The length property don't work as one will expect on arrays that are hashtables or associative arrays. This property only works as one will expect on numeric indexed arrays (and normalized, i.e, without holes). But there exists a way for get the length of an associative array, first you have to get the list of keys from the associative array using Object.keys(arr) and then you can use the length property over this list (that is a normalized indexed array). Like on the next example:
arr=[];
arr[0]={"zero": "apple"};
arr[1]={"one": "orange"};
arr["fancy"]="what?";
console.log(Object.keys(arr).length);
And about this next question:
not able to get all values while doing console.log(JSON.stringify(arr))
Your arr element don't have the correct format to be a JSON. If you want it to be a JSON check the syntax on the next example:
jsonObj = {};
jsonObj[0] = {"zero": "apple"};
jsonObj[1] = {"one": "orange"};
jsonObj["fancy"] = "what?";
console.log(Object.keys(jsonObj).length);
console.log(JSON.stringify(jsonObj));
From MDN description on arrays, here, "Arrays cannot use strings as element indexes (as in an associative array) but must use integers."
In other words, this is not Javascript array syntax
arr["fancy"]="what?";
Which leads to the error in .length.

How length property of an array works in javascript?

In javascript is it possible to directly set length property of an array. For example I can shorten the array like this:
var arr = [1, 2, 3, 4];
arr.length = 2;
console.log(arr); // Array [ 1, 2 ]
// arr[2], arr[3] are deleted
I would expect the length to be read-only (e.g. as in java).
As I understand, array in javascript is also an object:
console.log(typeof [1, 2, 3]); // object
How the length property works under the hood?
Array.prototype.length is a writable property of array.
Adding description from Array#lengthMDN
You can set the length property to truncate an array at any time. When you extend an array by changing its length property, the number of actual elements does not increase; for example, if you set length to 3 when it is currently 2, the array still contains only 2 elements. Thus, the length property does not necessarily indicate the number of defined values in the array.
This can be used to shorten an array.
As I understand, array in javascript is also an object:
console.log(typeof [1, 2, 3]); // object
Check Why does typeof array with objects return "Object" and not "Array"?
Generally, the length property is determined based on the highest index in the array.
Reading the length
1) For a dense array, this means that the length corresponds strictly to the number of elements:
var fruits = ['orange', 'apple', 'banana']; //fruits is a dense array
fruits.length // prints 3, the real count of elements
fruits.push('mango');
fruits.length // prints 4, one element was added
var empty = [];
empty.length // prints 0, empty array
The dense array does not have empties and the number of items corresponds to highestIndex + 1. In [3, 5, 7, 8] the highest index is 3 of element 8, thus the array size is 3 + 1 = 4.
2) In a sparse array (which has empties), the number of elements does not correspond to length value, but still is determined by the highest index:
var animals = ['cat', 'dog', , 'monkey']; // animals is sparse
animals.length // prints 4, but real number of elements is 3
var words = ['hello'];
words[6] = 'welcome'; //the highest index is 6. words is sparse
words.length //prints 7, based on highest index
Modifying the length
1) Modifying the property leads to cut the elements (if the new value is smaller than the highest index):
var numbers = [1, 3, 5, 7, 8];
numbers.length = 3; // modify the array length
numbers // prints [1, 3, 5], elements 7 and 8 are removed
2) or creating a sparse array (if the new value is bigger than the highest index):
var osTypes = ['OS X', 'Linux', 'Windows'];
osTypes.length = 5; // creating a sparse array. Elements at indexes 3 and 4
// do not exist
osTypes // prints ['OS X', 'Linux', 'Windows', , , ]
Please read this post, which covers in details everything about the length of an array.
The necessary steps to be fulfilled when length is being set on an Array is described on section 9.4.2.4 ArraySetLength of ES 6.0 language specification.
It deletes index properties from the old length of the array to the new length of the array according to step 19:
While newLen < oldLen repeat,
Set oldLen to oldLen – 1.
Let deleteSucceeded be A.[[Delete]](ToString(oldLen)).
Assert: deleteSucceeded is not an abrupt completion.
If deleteSucceeded is false, then
Set newLenDesc.[[Value]] to oldLen + 1.
If newWritable is false, set newLenDesc.[[Writable]] to false.
Let succeeded be OrdinaryDefineOwnProperty(A, "length", newLenDesc).
Assert: succeeded is not an abrupt completion.
Return false.
Whenever you set up a property for the array, it checks first to see if the property is length and if it is, it's supposed to do the ArraySetLength operation. This operation is described in section 9.4.2.1 [[DefineOwnProperty]] (this for Array Exotic Objects).
When the [[DefineOwnProperty]] internal method of an Array exotic object A is called with property key P, and Property Descriptor Desc the following steps are taken:
Assert: IsPropertyKey(P) is true.
If P is "length", then
Return ArraySetLength(A, Desc).
If it's not length and it's an indexed property the other steps in the operation are performed. Basically sets the value of the length property to the value of the index property + 1. As far as I can tell, the ArraySetLength operation isn't being used here to set the new length of the array.
Is it possible ?
Yes, it's possibly to directly set length property of an array.
You could set length of array to be shorten or higher ( to create sparsed array) then existing array. That's the way you can achieve desired behaviour.
Information about that from First edition of EcmaScript Standart-262, 15.4 :
Specifically, whenever a property is added whose name is an array
index, the length property is changed, if necessary, to be one more
than the numeric value of that array index; and whenever the length
property is changed, every property whose name is an array index whose
value is not smaller than the new length is automatically deleted.
So when you assigned lower value of length than it was before for an array deleting items( they would be collected by garbage collector ECMA-262, 15.4.5.1) from that array.
How length determined ?
The length of an array is the highest index + 1. However you can update length of Array in both direction ( decrease to delete elements and increase to create sparsed Array).
How to create Array with defined length ?
To create Array with defined length, pass length value into Array constructor as in code below:
var length = 10;
var newArray = Array(length);
console.log(newArray.length);// 10
Use full links:
Mozzilla MDN Array
Delete JavaScript Array Elements
W3Schools JavaScript Array length Property
arr = arr.slice(0, 2); will give you the first 2 elements of the array as a new array.
I would say this method is much more clear than using array.length = number to set the length of an array.
From the specs :
Array objects give special treatment to a certain class of property
names.
...
whenever a property of an Array object is created or changed, other
properties are adjusted as necessary to maintain this invariant.
Basically since the built-in array methods like join, slice, indexOf, etc all get affected by the property change, it is also necessary to update the Array.
In this case, since length is changed, there will be a change to the keys in the array.
You may take a look to Paragraph 9.4.2: Array Exotic Objects, and subsequent paragraph "ArraySetLength" where is described the algorithm:
An Array object is an exotic object that gives special treatment to array index property keys (see 6.1.7). A
property whose property name is an array index is also called an element. Every Array object has a length
property whose value is always a nonnegative integer less than 2 32 . The value of the length property is
numerically greater than the name of every own property whose name is an array index; whenever an own
property of an Array object is created or changed, other properties are adjusted as necessary to maintain this
invariant. Specifically, whenever an own property is added whose name is an array index, the value of the
length property is changed, if necessary, to be one more than the numeric value of that array index; and
whenever the value of the length property is changed, every own property whose name is an array index
whose value is not smaller than the new length is deleted. This constraint applies only to own properties of an
Array object and is unaffected by length or array index properties that may be inherited from its prototypes.

What is an efficient way to combine 2 numbers to use as the key of an object?

I am using the following pattern to index an injection from pairs of numbers to numbers:
var myHash = {};
...
for (... billion of iterations ...)
var x = someNum;
var y = otherNum;
myHash[x + "," + y] = z;
The problem with this code is that I'm using a string as the key of myHash, which has been tested to be much slower than integer keys. My question is: what is a more intelligent way to combine 2 numbers before using them as keys of an object? I.E., how to combine 2 doubles into an unique Integer?
There is the definition of an array in JavaScript:
Array objects give special treatment to a certain class of property names. A property name P (in the form of a String value) is an array index if and only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to 232 - 1. A property whose property name is an array index is also called an element. Every Array object has a length property whose value is always a nonnegative integer less than 232. The value of the length property is numerically greater than the name of every property whose name is an array index; whenever a property of an Array object is created or changed, other properties are adjusted as necessary to maintain this invariant. Specifically, whenever a property is added whose name is an array index, the length property is changed, if necessary, to be one more than the numeric value of that array index; and whenever the length property is changed, every property whose name is an array index whose value is not smaller than the new length is automatically deleted. This constraint applies only to own properties of an Array object and is unaffected by length or array index properties that may be inherited from its prototypes.
In other words, if the index you specify is a number representing an integer between 0 and 0xFFFFFFFE, then it is used as an array index. Any other value is taken as a string and it is used to create an object member instead of an array item.
So if you have constraints on your indices which would fit the valid range (0 to 0xFFFFFFFE) then you're good. Otherwise, what you have is probably the fastest.
So the following represents string indices which are members of object myHash:
myHash[x + "," + y] = z;
Someone mentioned using an array of arrays. That would not help you. You'd get many arrays instead of many strings. It would probably be about the same if not slower. The idea is something like this:
myHash[x] = []; // initialize the sub-array (must be done only once per value of 'x'
myHash[x][y] = z; // save z in that array
I do not recommend the double array because it will initialize one array for each value of 'x' on top of myHash and that probably not any faster than having the string concatenation (especially because you'll have to test whether the myHash[x] array was already defined or not...).
So... it is possible to write:
myHash[3.3] = "that worked?";
But if after that you check out the length, you'll notice it is zero:
console.log("Hash length = " + myHash.length);
This is because 3.3 is not an integer.

JavaScript Array Length Key Value

Why is this extremely basic JavaScript array giving me a length of 13 when there are only 3 key/value pairs in it. It makes sense that it might think 13 as 0 based index and my last array has a key of 12, but I need to have any array that has a key/value pair that returns me the correct number of pairs. The keys need to be numbers.
http://jsfiddle.net/fmgc8/1/
EDIT: this is how I solved it thanks.
http://jsfiddle.net/fmgc8/4/
it's because the highest number you have is:
array['12'] = 'twelve';
This creates an array length of 13 (since it's 0 based). JavaScript will expand the array to allocate the number of spots it needs to satisfy your specified slots. array[0..9] is there, you just haven't placed anything in them.
There is no diffrence between array['12'] and array[12] (array['12'] is not considered as associative array element). To find associative array length
The length property of arrays returns the biggest non-negative numeric key of the object, plus one. That's just the way it's defined.
If you want to count the key-value pairs, you're going to have to count them yourself (either by keeping track of them as they are added and removed, or by iterating through them).
Or, rearrange your array like this:
var array = [];
array.push(['10','ten']);
array.push(['11','eleven']);
array.push(['12','twelfe']);
alert(array.length);

Categories

Resources