Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Obtain the same result as a for..in loop, without any for..in loop

(Let us suppose that there is a good reason for wishing this. See the end of the question if you want to read the good reason.)

I would like to obtain the same result as a for in loop, but without using that language construct. By result I mean only an array of the property names (I don't need to reproduce the behavior that would happen if I modify the object while iterating over it).

To put the question into code, I'd like to implement this function without for in:

function getPropertiesOf(obj) {
  var props = [];
  for (var prop in obj)
    props.push(prop);
  return props;
}

From my understanding of the ECMAScript 5.1 specification about the for in statement and the Object.keys method, it seems the following implementation should be correct:

function getPropertiesOf(obj) {
  var props = [];
  var alreadySeen = {};

  // Handle primitive types
  if (obj === null || obj === undefined)
    return props;
  obj = Object(obj);

  // For each object in the prototype chain:
  while (obj !== null) {
    // Add own enumerable properties that have not been seen yet
    var enumProps = Object.keys(obj);
    for (var i = 0; i < enumProps.length; i++) {
      var prop = enumProps[i];
      if (!alreadySeen[prop])
        props.push(prop);
    }

    // Add all own properties (including non-enumerable ones)
    // in the alreadySeen set.
    var allProps = Object.getOwnPropertyNames(obj);
    for (var i = 0; i < allProps.length; i++)
      alreadySeen[allProps[i]] = true;

    // Continue with the object's prototype
    obj = Object.getPrototypeOf(obj);
  }

  return props;
}

The idea is to walk explicitly the prototype chain, and use Object.keys to get the own properties in each object of the chain. We exclude property names already seen in previous objects in the chain, including when they were seen as non-enumerable. This method should even respect the additional guarantee mentioned on MDN:

The Object.keys() method returns an array of a given object's own enumerable properties, in the same order as that provided by a for...in loop [...].

(emphasis is mine)

I played a bit with this implementation, and I haven't been able to break it.

So the question:

Is my analysis correct? Or am I overlooking a detail of the spec that would make this implementation incorrect?

Do you know another way to do this, that would match the implementation's specific order of for in in all cases?

Remarks:

  • I don't care about ECMAScript < 5.1.
  • I don't care about performance (it can be disastrous).

Edit: to satisfy @lexicore's curiosity (but not really part of the question), the good reason is the following. I develop a compiler to JavaScript (from Scala), and the for in language construct is not part of the things I want to support directly in the intermediate representation of my compiler. Instead, I have a "built-in" function getPropertiesOf which is basically what I show as first example. I'm trying to get rid of as many builtins as possible by replacing them by "user-space" implementations (written in Scala). For performance, I still have an optimizer that sometimes "intrinsifies" some methods, and in this case it would intrinsify getPropertiesOf with the efficient first implementation. But to make the intermediate representation sound, and work when the optimizer is disabled, I need a true implementation of the feature, no matter the performance cost, as long as it's correct. And in this case I cannot use for in, since my IR cannot represent that construct (but I can call arbitrary JavaScript functions on any objects, e.g., Object.keys).

like image 683
sjrd Avatar asked Oct 18 '14 22:10

sjrd


People also ask

How to use for in loop in JavaScript?

Definition and Usage. The for...in statements combo iterates (loops) over the properties of an object. The code block inside the loop is executed once for each property.

What is the difference between for in and for OF in JavaScript?

Both for...in and for...of statements iterate over something. The main difference between them is in what they iterate over. The for...in statement iterates over the enumerable string properties of an object, while the for...of statement iterates over values that the iterable object defines to be iterated over.

Does for of loop maintain order in JavaScript?

Using a for loop with arrays This means that when using the for loop, you can move forwards and backwards, change items in the array, add items, and more, while still maintaining the order of the array. The following statement creates a loop that iterates over an array and prints its values to the console.

How do you iterate over an object?

There are two methods to iterate over an object which are discussed below: Method 1: Using for…in loop: The properties of the object can be iterated over using a for..in loop. This loop is used to iterate over all non-Symbol iterable properties of an object.


1 Answers

From the specification point of view, your analysis correct only under assumption that a particular implementation defines a specific order of enumeration for the for-in statement:

If an implementation defines a specific order of enumeration for the for-in statement, that same enumeration order must be used in step 5 of this algorithm.

See the last sentence here.

So if an implementation does not provide such specific order, then for-in and Object.keys may return different things. Well, in this case even two different for-ins may return different things.

Quite interesting, the whole story reduces to the question if two for-ins will give the same results if the object was not changed. Because, if it is not the case, then how could you test "the same" anyway?

In practice, this will most probably be true, but I could also easily imagine that an object could rebuild its internal structure dynamically, between for-in calls. For instance, if certain property is accessed very often, the implementation may restructure the hash table so that access to that property is more efficient. As far as I can see, the specification does not prohibit that. And it is also not-so-unreasonable.

So the answer to your question is: no, there is no guarantee according to the specification, but still will probably work in practice.

Update

I think there's another problem. Where is it defined, what the order of properties between the members of the prototype chain is? You may get the "own" properties in the right order, but are they merged exactly the way as you do it? For instance, why child properties first and parent's next?

like image 182
lexicore Avatar answered Sep 19 '22 23:09

lexicore