Enumerability
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.
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:
- Something that won’t matter in the long run. But then why are we introducing new constructs that take it into consideration?
- 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.
- 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?
- 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 withJSON.parse
, I just have to recursively reconstruct the_parent
back-references; - etc.
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
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
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:
- Completely protecting data from “external” access. In ES6, one can use WeakMaps and closures for this.
- 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.
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.
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 ofNode
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 withJSON.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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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...
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
- 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.
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
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 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.
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.
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.
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?
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
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.
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.
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. :(
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.
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();
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.
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?
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.
I’m still really unhappy about enumerability in ECMAScript. I find it frustratingly inconsistent:
length
of arrays.constructor
is non-enumerable (as it is by default in all functions).I’ve seen various explanations for what enumerability is:
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.