20

I identified a bug in my code which I hope to solve with minimal refactoring effort. This bug occurs in Chrome and Opera browsers. Problem:

var obj = {23:"AA",12:"BB"};
//iterating through obj's properties
for(i in obj)
  document.write("Key: "+i +" "+"Value: "+obj[i]);

Output in FF,IE Key: 23 Value: AA Key: 12 Value: BB

Output in Opera and Chrome (Wrong)
Key: 12 Value BB
Key: 23 Value AA

I attempted to make an inverse ordered object like this

var obj1={"AA":23,"BB":12};
for(i in obj1)
  document.write("Key: "+obj[i] +" "+"Value: "+i);

However the output is the same. Is there a way to get for all browser the same behaviour with small changes?

Eugeniu Torica
  • 7,484
  • 12
  • 47
  • 62
  • You shouldn't give properties names that violate standard identifier rules (ie, starting a name with a number) – 3Dave Apr 15 '10 at 16:42
  • 1
    Actually it's fine: PropertyName in an ObjectLiteral may be a NumericLiteral. See ECMA262-5 section 11.1.5. Personally I'd prefer to quote it as `'23'` to make it clear that an object property is always a string, but either way is valid. – bobince Apr 15 '10 at 16:48
  • Good answers here http://stackoverflow.com/questions/5525795/does-javascript-guarantee-object-property-order – Déjà vu Jun 01 '14 at 07:51

5 Answers5

22

No. JavaScript Object properties have no inherent order. It is total luck what order a for...in loop operates.

If you want order you'll have to use an array instead:

var map= [[23, 'AA'], [12, 'BB']];
for (var i= 0; i<map.length; i++)
    document.write('Key '+map[i][0]+', value: '+map[i][1]);
bobince
  • 528,062
  • 107
  • 651
  • 834
2

I think you'll find the only reliable way to do this would be to use an array rather than an associative array, eg:

var arr = [{key:23,val:"AA"},{key:12,val:"BB"}];
for(var i=0; i<arr.length; i++)
  document.write("Key: "+arr[i].key +" "+"Value: "+arr[i].val);
Graza
  • 5,010
  • 6
  • 32
  • 37
  • JavaScript has objects with properties, not associative arrays. If they were arrays, they would be ordered. – Álvaro González Apr 15 '10 at 16:50
  • Agreed - but objects with properties are often (not entirely correctly I'll admit) called "associative arrays", or at least *used* for that purpose. "Associative arrays" (in a more general, not javascript sense) are usually not considered to equate to ordered values - they are considered to be a collection of name/value pairs. – Graza Apr 15 '10 at 17:07
2

According to the ES2015 spec, numeric keys are iterated in numeric order, and non-numeric keys are iterated in insertion order. This is guaranteed by the spec so you can rely on it.

If you want to always use insertion order, even for numeric keys, then you must add a prefix (such as _) to make them non-numeric:

var obj = {_23:"AA",_12:"BB"};
//iterating through obj's properties
for(i in obj)
  document.write("Key: "+i +" "+"Value: "+obj[i]);
Pauan
  • 2,506
  • 1
  • 20
  • 19
1

@bobince is right, Objects don't keep any ordering metadata.

In my case it didn't make sense to refactor to an array, so I submit another solution: create an array with your ordering and use it to map your object properties in order:

const obj = {
    'r': '#f00',
    'g': '#0f0',
    'b': '#00f',
};
const objMap = ['b','r','g'];

objMap.map((key, index) => {
    console.log(`array index: ${index}`);
    console.log(`object index: ${key}`);
    console.log(`object property: ${obj[key]}\n`);
});

Output:

array index: 0
object index: b
object property: #00f

array index: 1
object index: r
object property: #f00

array index: 2
object index: g
object property: #0f0
Gerbus
  • 2,554
  • 27
  • 22
-1

I did not get your result when the object property identifiers were alphabetic strings. IE8, FF5, Chrome 12, and Opera 9.8 all kept the creation order, i.e.

Key: AA Value: 23 Key: BB Value: 12

It was when the identifiers were numbers that the results matched yours:-

IE8, FF5 --> Key: 23 Value: AA Key: 12 Value: BB

Opera, Chrome --> Key: 12 Value: BB Key: 23 Value: AA

Opera and Chrome are storing in reverse of creation order because 12 is less than 23, so if you use instead say:-

var obj = {2:"AA",12:"BB"};

then you get this result for all 4 browsers:-

Key: 2 Value: AA Key: 12 Value: BB

So it is the use of numbers as identifiers that causes the discrepancy. If identifiers are alphabetic then both creation and stored property orders are the same for the 4 browsers. This is despite ECMA rules that the stored order need not be related to creation order.

If the string identifiers are numeric e.g. '23' and '12', then Opera and Chrome treat them as numbers and again reverse the creation order, so that type are not permitted either. '23a' types are OK as are 'a23' type.

To answer your question, use non-numeric string identifiers and the behaviour will be the same across the 4 browsers (and possibly all browsers).

John
  • 47
  • 1
  • 5
    The ECMAScript specification states that the order is undefined, it's doesn't mean it shouldn't be in order. It means if a browser decides to change is underlying data structure and happens to return the key to you in a different order, it's perfectly OK by the specification. That's why you shouldn't rely on this even if now it works well. You also have to keep in mind that the implementation can change from a platform to an other and it can also change in each version of a browser. – HoLyVieR Jul 03 '11 at 03:11
  • @HoLyVieR The ES2015 spec now guarantees that non-numeric keys are always iterated in insertion order. Numeric keys are iterated in numeric order. – Pauan Jan 25 '23 at 15:46