Enumerability

# Axel Rauschmayer (9 years ago)

I’m still really unhappy about enumerability in ECMAScript. I find it frustratingly inconsistent:

  • At the moment, only Object.keys and the for-in loop are affected by it.
  • In ECMAScript 6, Object.assign will also ignore non-enumerable properties.
  • Built-in prototype methods are non-enumerable, as is property length of arrays.
  • In ECMAScript 6, prototype methods created by classes are enumerable, the prototype method constructor is non-enumerable (as it is by default in all functions).

I’ve seen various explanations for what enumerability is:

  1. Something that won’t matter in the long run. But then why are we introducing new constructs that take it into consideration?
  2. Something that one has to do properly so that old code doesn’t break. Here, it’d be helpful to be concrete: where can this happen? The obvious case is for-in for instances of built-in constructors.
  3. Signaling an intent of sharing. But what does that mean in practice? Why are built-in methods different from user-defined methods in this regard?
  4. A way of marking a method as private. Again: why the divergence between built-in methods and user-defined methods?

I think it would really help the design of ECMAScript going forward if we had a definitive and complete explanation of what enumerability is now and what it should be in the future. I’m trying to make sense of it and to explain it to others and continue to fail.

# Brendan Eich (9 years ago)

Axel Rauschmayer wrote:

  • At the moment, only Object.keys and the for-in loop are affected by it.
  • In ECMAScript 6, Object.assign will also ignore non-enumerable properties.

Pave that cowpath!

  • Built-in prototype methods are non-enumerable, as is property length of arrays.

Good, more consistency with existing objects.

  • In ECMAScript 6, prototype methods created by classes are enumerable, the prototype method constructor is non-enumerable (as it is by default in all functions).

This may be a mistake. Prototype methods defined in JS are enumerable up to ES5 unless you use Object.defineProperty. Prototype methods on builtins are not enumerable. Something has to give.

# Claude Pache (9 years ago)

Le 9 janv. 2014 à 12:21, Axel Rauschmayer <axel at rauschma.de> a écrit :

I’m still really unhappy about enumerability in ECMAScript. I find it frustratingly inconsistent:

  • At the moment, only Object.keys and the for-in loop are affected by it.
  • In ECMAScript 6, Object.assign will also ignore non-enumerable properties.

You forgot an important fact: While legacy for/in loop takes inherited properties into account, all newer constructs are only affected by own enumerable properties. You also forgot to mention JSON.stringify, which also takes only own enumerable properties into account. I guess that, in the long run, enumerability will be really significant for own properties only.

  • Built-in prototype methods are non-enumerable, as is property length of arrays.
  • In ECMAScript 6, prototype methods created by classes are enumerable, the prototype method constructor is non-enumerable (as it is by default in all functions).

I’ve seen various explanations for what enumerability is:

  1. Something that won’t matter in the long run. But then why are we introducing new constructs that take it into consideration?
  2. Something that one has to do properly so that old code doesn’t break. Here, it’d be helpful to be concrete: where can this happen? The obvious case is for-in for instances of built-in constructors.
  3. Signaling an intent of sharing. But what does that mean in practice? Why are built-in methods different from user-defined methods in this regard?
  4. A way of marking a method as private. Again: why the divergence between built-in methods and user-defined methods?

I think it would really help the design of ECMAScript going forward if we had a definitive and complete explanation of what enumerability is now and what it should be in the future. I’m trying to make sense of it and to explain it to others and continue to fail.

I can't provide a definite abstract explanation, but here is a practical situation where I've found non-enumerability a useful feature, although it does not exactly fit any of the four explanations you cited. Consider some tree structure:

obj = { foo: 1, bar: 2, children : [ { foo: 5, bar: 4 }, { foo: 2, bar: 4, children: [...] } ] }

When walking through such a tree, I typically want to have access (for example) to the parent of a node (just like the parentNode method of Node objects in the DOM). For that end, I define a non-enumerable _parent property on each node, which refers back to its parent node. Now, although I have polluted each node with a supplementary property,

  • I can happily invoke Object.keys on a node, and it will give me back the correct answer;
  • I can happily serialise my tree using JSON.stringify, and it won't complain that it can't handle cyclic structures. After deserialising it with JSON.parse, I just have to recursively reconstruct the _parent back-references;
  • etc.
# Andrea Giammarchi (9 years ago)

I hope this won't be considered spam and slightly OT but you can already happily serialize with an enumerable parent via reviver and serializer functions or simply using CircularJSON which seems to be ideal for your very specific case so that you don't have to do anything once deserialized in order to have exactly same structure back.

I mostly agree with Axel, specially on the "class" side. I have a redefine utility that indeed defines by default all class properties and methods not enumerable since own property is usually what matters, the class should not affect for/in at all as we expect with any other native class.

+1 for non enumerable by default ... class are a new thing in any case, these could desugar to Object.defineProperties in the class prototype instead of simply assigning them.

I know TypeScript chaps will hate me for such idea

# Allen Wirfs-Brock (9 years ago)

On Jan 10, 2014, at 11:49 AM, Brendan Eich wrote:

Axel Rauschmayer wrote:

  • Built-in prototype methods are non-enumerable, as is property length of arrays.

Good, more consistency with existing objects.

  • In ECMAScript 6, prototype methods created by classes are enumerable, the prototype method constructor is non-enumerable (as it is by default in all functions).

This may be a mistake. Prototype methods defined in JS are enumerable up to ES5 unless you use Object.defineProperty. Prototype methods on builtins are not enumerable. Something has to give.

The ES6 class specification originally made prototype methods (we didn't have static methods at the time) non-enumerable.

That was changed at the Sept 19, 2012 TC39 meeting esdiscuss/2012-September/025231

Concise Method Definition, Revisited

RW: Defaulting concise methods to non-enumerable is a mistake

DH: Not sure about the decision to go non-enumerable. Users expect that things they create to be enumerable and things that the platform provides to be non-enumerable.

LH: enumerability is not a real concept with any sort of meaning.

EA: (reveals the broken-ness of the DOM)

No longer arguable.

Conclusion/Resolution Concise method definitions create [[Enumerable]]: true

I went along with the majority, although I don't really like it very much.

It's basically a question of whether class definitions follow the pattern of pre-ES6 built-in or the pattern of pre-ES6 manually constructed constructor/prototype pairs.

Early on we decided to follow the built-in pattern, but at that meeting we changed it WRT enumerability of methods.

It comes down to this

class C extends Array {
   foo() {}
}

let c = {foo() {}}

let assignedClass = Object.assign({ }, C.prototype);
let assignedObjLit = Object.assign({ }, c);
original class design:
  typeof assignedClass.foo   //Undefined
  typeof assignedObjLit.foo  //Undefined

current ES6 spec:
  typeof assignedClass.foo   //function
  typeof assignedObjLit.foo  //function

another plausible design:
  typeof assignedClass.foo   //Undefined
  typeof assignedObjLit.foo  //function
# Axel Rauschmayer (9 years ago)

The ES6 class specification originally made prototype methods (we didn't have static methods at the time) non-enumerable.

That was changed at the Sept 19, 2012 TC39 meeting esdiscuss/2012-September/025231

It may make sense if enumerability was repurposed as a runtime flag for privacy (in the sense of “you don’t need to know about this property/method”). That would nicely complement TypeScript’s static-only (= non-runtime) private keyword. It’s one of the two – largely orthogonal – use cases I see for privacy:

  1. Completely protecting data from “external” access. In ES6, one can use WeakMaps and closures for this.
  2. Encapsulation: Hide internal properties from sight, document internal-ness. If it’s advisory only then you can still write “friend” functions etc, without too much of a fuss.

I’m looking for a simple explanation of what enumerability will be, going forward. If there isn’t one then I’d argue that no new feature should be influenced by it.

# Allen Wirfs-Brock (9 years ago)

On Jan 10, 2014, at 2:40 PM, Axel Rauschmayer wrote:

I’m looking for a simple explanation of what enumerability will be, going forward. If there isn’t one then I’d argue that no new feature should be influenced by it.

That was one of the argument made in favor of concise methods defaulting as not enumerable: enumerable really only controls whether a property shows up in for-in (and a couple closely related reflection functions) and for-in has been essentially deprecated and replace by for-of. That makes the enumerable attribute an obsolete feature and we should be trying to give it meaning for new features. Hence, just always default it to true.

# Axel Rauschmayer (9 years ago)

I think it would really help the design of ECMAScript going forward if we had a definitive and complete explanation of what enumerability is now and what it should be in the future. I’m trying to make sense of it and to explain it to others and continue to fail.

I can't provide a definite abstract explanation, but here is a practical situation where I've found non-enumerability a useful feature, although it does not exactly fit any of the four explanations you cited. Consider some tree structure:

obj = { foo: 1, bar: 2, children : [ { foo: 5, bar: 4 }, { foo: 2, bar: 4, children: [...] } ] }

When walking through such a tree, I typically want to have access (for example) to the parent of a node (just like the parentNode method of Node objects in the DOM). For that end, I define a non-enumerable _parent property on each node, which refers back to its parent node. Now, although I have polluted each node with a supplementary property,

  • I can happily invoke Object.keys on a node, and it will give me back the correct answer;
  • I can happily serialise my tree using JSON.stringify, and it won't complain that it can't handle cyclic structures. After deserialising it with JSON.parse, I just have to recursively reconstruct the _parent back-references;
  • etc.

Nice example. Data versus meta-data, in line with the length of an array being non-enumerable.

# Brendan Eich (9 years ago)

Axel Rauschmayer wrote:

Nice example. Data versus meta-data, in line with the length of an array being non-enumerable.

Yes, and that's the primordial reason for enumerability. But it still seems that class declaration users might want methods on the prototype to be non-enumerable. Sauce for the goose (built-ins).

# Axel Rauschmayer (9 years ago)

That was one of the argument made in favor of concise methods defaulting as not enumerable: enumerable really only controls whether a property shows up in for-in (and a couple closely related reflection functions) and for-in has been essentially deprecated and replace by for-of. That makes the enumerable attribute an obsolete feature and we should be trying to give it meaning for new features. Hence, just always default it to true.

Good point. That “meaning for new features” should probably be clearly stated and dictate how Object.assign() behaves.

I know this runs counter the conventional wisdom for specs, but I find design rationales incredibly important for making sense of what’s going on: The answers and discussions on this mailing list were essential in helping me understand the language.

# Brendan Eich (9 years ago)

Axel Rauschmayer wrote:

I know this runs counter the conventional wisdom for specs, but I find design rationales incredibly important for making sense of what’s going on: The answers and discussions on this mailing list were essential in helping me understand the language.

+1.

# Andrea Giammarchi (9 years ago)

I would like to see some Rick or David example about the "expected to be enumerable".

If that's about knowing if a class is native or not, looping with a for/in its prototype or any instance does not seem to mean anything reliable since even native methods can be redefined and made enumerable.

If that's the case I would rather consider a simple Object.isNative(generic) proposal that would simply return true for all native things that would return [native code] via {}.toString.call(generic)

enumerability does not seem to help anyone if not forcing developers believe that every for/in loop should have a .hasOwnProperty() check so that methods should be discarded, as well as shared data properties.

I am basically asking for a real-world example, if possible, where prototype methods/properties enumerability is neede/wanted/expected, thank you.

# Allen Wirfs-Brock (9 years ago)

On Jan 11, 2014, at 9:01 AM, Axel Rauschmayer wrote:

Good point. That “meaning for new features” should probably be clearly stated and dictate how Object.assign() behaves.

I know this runs counter the conventional wisdom for specs, but I find design rationales incredibly important for making sense of what’s going on: The answers and discussions on this mailing list were essential in helping me understand the language.

I happily put in informative NOTE's when it seems important to clarify intent for implementors (and even future spec. editors). Feel free to file bugs any time you find a place in the spec. where you think something needs to be clarified in that manner.

You guys who read the drafts are the beta testers of the specification and your feedback is really useful for finding places where the clarity needs to be improved.

# Allen Wirfs-Brock (9 years ago)

On Jan 11, 2014, at 9:24 AM, Andrea Giammarchi wrote:

I would like to see some Rick or David example about the "expected to be enumerable".

If that's about knowing if a class is native or not, looping with a for/in its prototype or any instance does not seem to mean anything reliable since even native methods can be redefined and made enumerable.

It's not just prototypes. The same non-enumerability conventions are applied to built-in constructor properties (ie, class static methods).

If that's the case I would rather consider a simple Object.isNative(generic) proposal that would simply return true for all native things that would return [native code] via {}.toString.call(generic)

This has nothing to do with "native code". Built-ins can be self hosted using JS code and show that code (or not) in their toString. It's strictly a specification issue. The spec. says that, in general, built-in methods are non-enumerable and that non=built-in methods are enumerable.

# Axel Rauschmayer (9 years ago)

I happily put in informative NOTE's when it seems important to clarify intent for implementors (and even future spec. editors). Feel free to file bugs any time you find a place in the spec. where you think something needs to be clarified in that manner.

You guys who read the drafts are the beta testers of the specification and your feedback is really useful for finding places where the clarity needs to be improved.

Just to be clear: this one is not (necessarily) on you, you have your work cut out for you, anyway. I’m thinking more along the lines along a companion document. But NOTEs are a great idea, I’ll keep it in mind while reading the spec.

# David Bruant (9 years ago)

Le 11/01/2014 18:03, Brendan Eich a écrit :

Axel Rauschmayer wrote:

I know this runs counter the conventional wisdom for specs, but I find design rationales incredibly important for making sense of what’s going on: The answers and discussions on this mailing list were essential in helping me understand the language.

+1.

+2

Case in point: Allen point a couple of messages ago that for-in is effectively rendered useless in ES6 replaced by for-of.

# Andrea Giammarchi (9 years ago)

Thanks Allen, makes sense ... but still no example where enumerability of properties and method defined in a "class" prototype is useful for ... something I cannot imagine right now.

Anything?

I understand for-of replaces ... etc etc ... for-of is not easy to polyfill though while class sugar is straight forward.

# Allen Wirfs-Brock (9 years ago)

On Jan 11, 2014, at 9:57 AM, Andrea Giammarchi wrote:

Thanks Allen, makes sense ... but still no example where enumerability of properties and method defined in a "class" prototype is useful for ... something I cannot imagine right now.

Anything?

Examples that were mentioned in the discussion that lead to the change include abstraction meta methods such as extend or mixin that exist in the wild. These were created in an world where everything that was user defined was enumerable (at least by default).

This might be particularly problematic if concise methods in object literals are non-enumerable:

$.extend( obj, {
    foo () {},
    bar () {},
    baz: function () {}
});

it would be surprising if after this call obj only acquired a baz property. (assuming that extend is implemented using for-in or Object.keys).

It might be less surprising if we had

class Extension {
    foo () {}
    bar () {}
};

$.extend(obj, Extension.prototype);

but consider if that code had originally been:

function Extension() {}
Extension.prototype.foo = function () {};
Extension.prototype.bar = function () {};

$.extend(obj, Extension.prototype);

and somebody "cleaned up" the code by via an ES6 class declaration. Surprise, things break...

# Andrea Giammarchi (9 years ago)

I would leave Object.keys out of the "problem" since inherited properties would be ignored regardless not being "own"

It seems to me that reasons are ... that Object.mixin, which was not only checking enumerable properties but all own, would have solved the extend example with the prototype but thanks for that, I was not thinking about a prototype itself to mixin properties.

I would re-think this once Object.mixin will be in again, if ever

# Axel Rauschmayer (9 years ago)
  • At the moment, only Object.keys and the for-in loop are affected by it.
  • In ECMAScript 6, Object.assign will also ignore non-enumerable properties.

Pave that cowpath!

I’m sorry for being difficult, but IMO this is an important point: The current cowpath is for-in style, right? That is, inherited and own non-enumerable properties. We’ll deviate from that anyway. Independently of what enumerability means, I can only think of cases (including Claude Pache’s pro-enumerability example) where I would want to copy all own properties.

# Andrea Giammarchi (9 years ago)

Object.mixin was doing that through Object.getOwnPropertyNames ... that is what I would expect any $.extend would do too but I feel naive here ^_^

Niceness

# Brendan Eich (9 years ago)

Axel Rauschmayer wrote:

I’m sorry for being difficult, but IMO this is an important point: The current cowpath is for-in style, right? That is, inherited and own non-enumerable properties. We’ll deviate from that anyway. Independently of what enumerability means, I can only think of cases (including Claude Pache’s pro-enumerability example) where I would want to copy all own properties.

Why do you want an array's .length to be copied?

I thought the issue was rather own vs. inherited enumerables. If we restricted Object.assign or whatever to own, then we'd probably hit all targets, and enumerable prototype properties induced by class syntax per ES6 draft spec would be ok.

# Brendan Eich (9 years ago)

Brendan Eich wrote:

I can only think of cases (including Claude Pache’s pro-enumerability example) where I would want to copy all own properties.

Why do you want an array's .length to be copied?

Or in Claude's example, why should a non-enumerable _parent be copied?

Claude's words about the advantage of non-enumerable _parent not being copied were pretty clear. He was not advocating all own properties.

esdiscuss.org/topic/enumerability#content-2

# Rick Waldron (9 years ago)

On Sat, Jan 11, 2014 at 12:24 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

I would like to see some Rick or David example about the "expected to be enumerable".

If that's about knowing if a class is native or not, looping with a for/in its prototype or any instance does not seem to mean anything reliable since even native methods can be redefined and made enumerable.

Making concise method definitions default to enumerable: true had nothing to do with classes and everything to do with code that consumes objects with concise method definitions. Refactoring this:

  var o = {
    method: function() {
    }
  };

To this:

  var o = {
    method() {
    }
  };

Should absolutely not change the resulting behaviour of a for-in loop over o's properties. I don't know why Allen said that for-in has been deprecated in favor of for-of, since the latter doesn't imply iteration over the properties of a plain object. That's not going away.

# Rick Waldron (9 years ago)

On Sat, Jan 11, 2014 at 12:57 PM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:

Thanks Allen, makes sense ... but still no example where enumerability of properties and method defined in a "class" prototype is useful for ... something I cannot imagine right now.

Again, that was never the argument, but simply a by-product of making concise method definitions behave the same way as function expression definitions.

# Andrea Giammarchi (9 years ago)

Rick, what you wrote has nothing to do with inheritance, right? So why should "classes" methods be part of a for/in of an instance, which is not what you wrote, again?

# Andrea Giammarchi (9 years ago)

Recap: it seems we agree classes properties and methods should not show up in a for/in ... which I believe is the eldorado JS developers looked for since ever, hating that only native classes had that privilege ... so, once again why on earth classes methods and properties show up in a for/in since nobody ever wanted that?

I agree that the method definition for the object should not be affected, but it seems to me we mixed up two different things: a) the object method definition, and we all agree here b) inherited classes properties and methods ... and I guess we all dream about not disturbing/enumerable properties and/or methods in a for/in since that is not desired and forces indeed developers to think they need .hasOwnProperty() check all over which is an anti-pattern if you ask me, and indeed my class utility defines everything in the class as non-enumerable.

Thanks for clarifications

# Rick Waldron (9 years ago)

On Sun, Jan 12, 2014 at 11:45 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

Rick, what you wrote has nothing to do with inheritance, right? So why should "classes" methods be part of a for/in of an instance, which is not what you wrote, again?

Sometimes I find you incredibly frustrating to communicate with. You asked me to give an example of "expected to be enumerable" and I did so by providing an example in the form that I made my original argument for revisiting the decision to make concise method definitions non-enumerable. Specifying concise method definitions to be enumerable: true in all cases is consistent with defining a method on a prototype object today:

  function C() {}
  C.prototype.method = function() {};

  for (var p in C.prototype) {console.log(p)}

  > method

Refactor #1:

  function C() {}
  C.prototype = {
    constructor: C,
    method() {}
  };

  for (var p in C.prototype) {console.log(p)}

  > method

Refactor #2:

  class C {
    method() {}
  }

  for (var p in C.prototype) {console.log(p)}

  > method

Now that I've described as clearly as I possibly can, I hope the point of making concise method definitions—regardless of which syntactic body form they appear in—produce an enumerable: true property is clear to you and that my answer has sufficiently met your need.

# Rick Waldron (9 years ago)

On Sun, Jan 12, 2014 at 11:57 PM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:

Recap: it seems we agree classes properties and methods should not show up in a for/in ...

No we don't agree, because that's inconsistent with the behaviour that exists today.

# Andrea Giammarchi (9 years ago)

classes do not exist today and your example was about direct object, not inherited from classes as they are meant in ES6.

I see from both here and Twitter you took this in a very wrong way while I was asking rationale from those Allen's quotes you actually rephrased so I just stop here.

All good, for/in with classes properties/methods is all we wanted/needed in ES6, thanks.

# Boris Zbarsky (9 years ago)

On 1/12/14 11:57 PM, Andrea Giammarchi wrote:

Recap: it seems we agree classes properties and methods should not show up in a for/in

I should note that web developers commonly expect DOM methods/properties to show up in for/in (and in fact often write code depending on that, sometimes depending on the order of the enumeration). So to the extent that we're trying to align DOM stuff with ES6 classes, we may need a class-level switch to decide whether a particular class has enumerable members or something. :(

# Andrea Giammarchi (9 years ago)

If that's true then a switch seems over complicated so it's easier to keep enumerability as it is now: nobody will have surprises in this way.

# Allen Wirfs-Brock (9 years ago)

On Jan 13, 2014, at 6:46 AM, Boris Zbarsky wrote:

I should note that web developers commonly expect DOM methods/properties to show up in for/in (and in fact often write code depending on that, sometimes depending on the order of the enumeration). So to the extent that we're trying to align DOM stuff with ES6 classes, we may need a class-level switch to decide whether a particular class has enumerable members or something. :(

Whatever the default enumerability, you can always change it using Object.defineProperty and you can write a utility function that will does. Define the utility as a method on function prototype and you can say something like:

class Foo {};
Foo.enumerableMethods();
//or
Foo.nonenumerableMethods();
# Axel Rauschmayer (9 years ago)

Brendan Eich wrote:

I can only think of cases (including Claude Pache’s pro-enumerability example) where I would want to copy all own properties.

Why do you want an array's .length to be copied?

Or in Claude's example, why should a non-enumerable _parent be copied?

Claude's words about the advantage of non-enumerable _parent not being copied were pretty clear. He was not advocating all own properties.

esdiscuss.org/topic/enumerability#content-2

True. I was thinking shallow clone:

var copy = Object.assign({ __proto__: obj.__proto__ }, obj);

But Object.mixin() (or Object.define(), I thought the case for re-renaming it was convincing) is probably a better match here.

Will keys(), values() and entries() from module "@dict" ignore non-enumerable properties, too?

# Andrea Giammarchi (9 years ago)

Also true ... it's funny 'cause for ages we, developers, tried to make "ES3 classes" non enumerable so that for/in were not affected, we could extend (avoiding conflicts, etc) and now that we could define such behavior as default through a new ES6 class syntax we are "worried" that is not what developers want ... I thought the fact basically every piece of code based on .hasOwnProperty() check as first line of any for/in would be already convincing nobody wants class enumerability and indeed I usually define prototypes via defineProperties in a non enumerable way, but maybe I am the only that never wanted/needed inherited enumerability for classes.

That being said the single/multiple mixin through prototype would be a problem, if done pragmatically, but it can be easily solved with getOwnPropertyNames ?

Make classes methods/properties enumerable by default ... I am still not convinced is useful and I don't remember other OOP languages exposing classes properties within loops.

Anyway, not a big deal, nothing to go nuts about ... we can all keep coding anyway.