Compartilhar via


For-in Revisited

A while back I was discussing the differences between VBScript's For-Each and JScript's for-in loops.

A coworker asked me today whether there was any way to control the order in which the for-in loop enumerates the properties. He wanted to get the list in alphabetical order for some reason.

Unfortunately, we don't support that. The specification says (ECMA 262 Revision 3 section 12.6.4):

The mechanics of enumerating the properties is implementation dependent. The order of enumeration is defined by the object. Properties of the object being enumerated may be deleted during enumeration. If a property that has not yet been visited during enumeration is deleted, then it will not be visited. If new properties are added to the object being enumerated during enumeration, the newly added properties are not guaranteed to be visited in the active enumeration.

Enumerating the properties of an object includes enumerating properties of its prototype, and the prototype of the prototype, and so on, recursively; but a property of a prototype is not enumerated if it is “shadowed” because some previous object in the prototype chain has a property with the same name.

Our implementation enumerates the properties in the order that they were added. (This also implies that properties added during the enumeration will be enumerated.)

If you want to sort the keys then you'll have to do it the hard way -- which, fortunately, is not that hard. Enumerate them in by-added order, add each to an array, and sort that array.

var myTable = new Object();
myTable["blah"] = 123;
myTable ["abc"] = 456
myTable [1234] = 789;
myTable ["def"] = 346;
myTable [345] = 566;

var keyList = new Array();
for(var prop in myTable)
keyList.push(prop);
keyList.sort();

for(var index = 0 ; index < keyList.length ; ++index)
print(keyList[index] + " : " + myTable[keyList[index]]);

This has the perhaps unfortunate property that it sorts numbers in alphabetical, not numeric order. If that bothers you, then you can always pass a comparator to the sort method that sorts numbers however you'd like.

Comments

  • Anonymous
    January 05, 2007
    Strange.   Seems like all the keys are converted to strings.  Is that because they become field names on the object? Here's what I mean (sorry about the verbosity): //-------------------------------------------------------------------------
    //                               TestSorting()
    //-------------------------------------------------------------------------
    function TestSorting(){
      print();
      print('TestSorting()');
      print('-------------');
      var myTable = new Object();
      myTable["Blah"] = 123;
      myTable ["abc"] = 456;
      myTable [1234] = 789;
      myTable ["def"] = 346;
      myTable [345] = 566;
      var keyList = new Array();
      for(var prop in myTable)
         keyList.push(prop);
      keyList.sort( Comparer );
      for( var index in keyList )
         print( 'myTable[{0}] = {1}', keyList[index], myTable[keyList[index]] );
    }
    //-------------------------------------------------------------------------
    //                               Comparer()
    // Silly sort method of the collection seems to convert all the indices to
    // strings, so that it is not easy to sort the numbers separately from the
    // strings.
    //-------------------------------------------------------------------------
    function Comparer(a,b){
      var result = -1;
      print('Comparer(): {0} is a {1}, {2} is a {3}', a, typeof(a), b, typeof(b) );
      if( typeof(a) == 'number' )
      {
         print('{0} is a number.',a);
         if( typeof(b) == 'number' )
            result = a - b;
         else
            result = -1;
      }  else  {
         if( typeof(b) == 'number' )
            result = 1;
         else
         {
            if( a.toLowerCase() == b.toLowerCase() )
               result = 0;
            else
               if( a.toLowerCase() > b.toLowerCase() )
                  result = 1;
               else
                  result = -1;
         }
      }
      if( result == 0 )
         print( '{0} and {1} are equal.', a, b );
      else
         if( result < 0 )
            print( '{0} is less than {1}.', a, b );
         else
            print( '{0} is greater than {1}.', a, b );
      return result;
    }
    //-------------------------------------------------------------------------
    //                               print()
    //
    // Shorthand for WScript.echo() that also does positional parameter
    // substitution (as in C#).
    //-------------------------------------------------------------------------
    function print(msg){
      var args = print.arguments;
      if(args.length==0)
         var msg = ''; // Allows for "print();"
      else  {
         // Parameter substitution à la C#:
         for( var i = 1; i < args.length; i++ )
            while( msg.indexOf('{' + (i-1) + '}') >= 0 )
               msg = msg.replace( '{' + (i-1) + '}', '' + args[i] );
      }
      WScript.echo(msg);
    }
    TestSorting();

    The results printed out are:
    TestSorting()
    -------------
    Comparer(): def is a string, 345 is a string
    def is greater than 345.
    Comparer(): def is a string, abc is a string
    def is greater than abc.
    Comparer(): def is a string, 1234 is a string
    def is greater than 1234.
    Comparer(): def is a string, Blah is a string
    def is greater than Blah.
    Comparer(): abc is a string, 345 is a string
    abc is greater than 345.
    Comparer(): abc is a string, Blah is a string
    abc is less than Blah.
    Comparer(): Blah is a string, 1234 is a string
    Blah is greater than 1234.
    Comparer(): Blah is a string, 345 is a string
    Blah is greater than 345.
    Comparer(): abc is a string, 345 is a string
    abc is greater than 345.
    Comparer(): abc is a string, 1234 is a string
    abc is greater than 1234.
    Comparer(): abc is a string, 345 is a string
    abc is greater than 345.
    Comparer(): 345 is a string, 1234 is a string
    345 is greater than 1234.
    myTable[1234] = 789
    myTable[345] = 566
    myTable[abc] = 456
    myTable[Blah] = 123
    myTable[def] = 346

    So seems like it would by quite tedious to write all the code necessary to sort by keys such that the numeric keys are sorted numerically...

  • Anonymous
    January 05, 2007
    Yes, that was what I intended to imply by the last two sentences in the post.  All object property slot names are strings, whether they are initially numbers or not.   However, it is not particularly tedious to write a comparator which compares numbers numerically.  Just check both strings to see if they are numbers, if they are, compare them as numbers, otherwise compare them as strings.

  • Anonymous
    January 08, 2007
    Ah, I thought you were implying that the default sort would be alphabetical because the keys would be converted to string for sorting purposes, not that the keys were converted to string in the first place. The difference is that the comparer would be simpler in that you could simply check the type.     Since the comparer is getting all strings no matter what, you have to look at the string and assume what data type it was in the first place "123" may have been an int or it may have been the string "123". I was thinking that the key object type was maintained and would work like this does in Python, for example: myTable = {} myTable["Blah"] = 123 myTable ["abc"] = 456 myTable [1234] = 789 myTable ["def"] = 346 myTable [345] = 566 myTable ['345'] = 999  # This '345' key is distinct from the 345 entry. keys = myTable.keys() def KeyComparer( a, b ):   result = 0   if type(a) == type(b):      if type(a) == type(''):         result = cmp(a.lower(),b.lower())      else:         result = cmp( a, b )   elif type(a) == type(''):      result = -1   else:      result = 1   description = 'equal to'   if result > 0:      description = 'greater than'   elif result < 0:      description = 'less than'   print 'KeyComparer: %s is %s %s.' % (str(a),description, str(b))   return result keys.sort(KeyComparer) print 'Keys: %s' % keys So I guess in JScript you would need to use parseFloat or a regular expression and assume all numerical looking keys were originally numbers.

  • Anonymous
    December 20, 2007
    While we're on the subject of semantic differences between seemingly similar syntaxes, let me just take this opportunity to quickly answer a frequently asked question: why doesn't for in enumerate a collection?

  • Anonymous
    February 23, 2012
    Is there any similar code that could enumerate properties for ActiveXObjects, or would I have to use IUnknown in C++ ... code such as this just doesn't work!? var objCadImage = new ActiveXObject("TurboCAD.Drawing") for( n in objCadImage){ document.write(n+"<br>"); }