// Roughly: a combination of Jeremy’s and Allen’s ideas, updated with the results of recent discussions | |
// Guidelines: | |
// - Don’t use the same syntax as for object literals, but stay close. | |
// - Rationale: Some object literal features are forbidden in class declarations => don’t confuse people | |
// - Rationale: Comma separation is a bit tricky. | |
// - Keep new features at a minimum | |
// - Don’t obscure the fact that private names are actually name objects. | |
// => They can also be imported from somewhere else – a use case that needs to be supported. | |
// - In order to minimize confusion, keep module syntax and class declaration syntax similar. | |
// Rules: | |
// - Use @ to refer to name objects | |
// - there is no syntactic sugar for `this.` | |
// Name objects: | |
// - Rough rule for what @foo means: “insert arbitrary identifier here”. | |
// - Used for property access and to name methods. | |
// - foo.@bar is syntactic sugar for foo[bar] | |
// - Rationale: immediately obvious that it’s property access | |
class Monster extends Being { | |
// Only methods are allowed here (because these are the prototype’s properties) | |
// Create name objects that only exist within the declaration’s scope | |
private age, health, incAge; | |
// Constructor | |
// Alternative: use the name "new". Caveat: super.new(name) looks a bit weird. | |
constructor(name, this.weight, this.@age, this.@health) { | |
super.constructor(name); | |
} | |
getNameObjectForAge() { | |
return age; | |
} | |
// private method | |
@incAge() { | |
this.@age++; | |
} | |
// getter | |
get strength() { | |
// stronger with increasing age... | |
return super.strength * this.@age; | |
} | |
} |
module name from "@name"; | |
// Alternatives: use <| operator, use a do-block | |
let Monster = function () { | |
let age = name.create(); | |
let health = name.create(); | |
let incAge = name.create(); | |
function Monster(name, weight, age, health) { | |
super.constructor(name); | |
this.weight = weight; | |
this[age] = age; | |
this[health] = health; | |
} | |
Monster.prototype = Object.create(Being.prototype); | |
Monster.prototype.constructor = Monster; | |
Monster.prototype.getNameObjectForAge = function () { | |
return age; | |
}; | |
Monster.prototype[incAge] = function () { | |
this[age]++; | |
} | |
Object.defineProperty(Monster.prototype, "strength", { | |
get: function () { | |
// stronger with increasing age... | |
return super.strength * this[age]; | |
} | |
}); | |
return Monster; | |
}(); |
Agreed. But if that happens then [] completely changes its meaning from “property access" to “collection element access”. And we need Object.getProperty()
and Object.setProperty()
. I’ll leave the comment as is at the moment (because it works well as an explanation). Later, it can be changed to use the Object.*Property() methods.
@khs4473: I think the objection to a static keyword used in this manner was that it adds a lot of noise. But with static being rare, it could work well.
Note, though, that the parallel with private properties is not very strong, because private members and static members end up in different objects.
I think we will have super expressions in ES.next (separately from classes), so calling super.constructor is (imho) unnecessary (call super(...)) and brittle (they may have changed .constructor, it's property like others).
Can we have an example of the entire thing desugared?
I attempted to desugar it : https://gist.github.com/2151045
You forgot the return
statement in the desugared section
Is it
Monster.prototype = Object.create(Being);
or
Monster.prototype = Object.create(Being.prototype);
?
@Raynos: Fixed, thanks.
@domenic: You are absolutely right. Fixed. Error-prone, looking forward to syntactic sugar...
I think you have problem with the desugaring because all methods (but at least the constructor) are non-enumerable, and to match object-initializer, method (not the constructor) should also be non-writable (but configurable), so it would be easier to desugar to one big
Object.create(Being.prototype, {
...
})
Not to mention constructor-inheritance, do you plan it as part of this proposal (then you should use <| when defining Monster function),
@Herby where does it say whether methods should be non-enumerable (only the constructor property should be)
@Herby, @Raynos: I expect the decision on whether or not to make methods non-enumerable (which is done by built-ins, but not by most of the userland code that I have seen) to be final fine-tuning. My first solution was a big Object.create() with property descriptors, but then you have to use [] for the private property names (for which there isn’t consensus, yet).
@Raynos: In draft ES6 spec, it says about object initializers that when short method syntax is used, they are created with enumerable = false, configurable = true and writable = false.
writable
false, hmmm, that seems to go against even built-ins. I imagine most would be against it, but IMO it'd be a good thing to force something more explicit (i.e. Object.defineProperty
) when overriding existing methods.
@domenic, that's exactly the logic behind those attribute choices for concise methods. If somebody has defined a property as a "method" it should take something more than an assignment to change it.
However, many people find the configurable: true & writable: false combination baffling so it is going to take some effort to keep it in the spec.
I still don't like "foo.@bar is syntactic sugar for foo[bar]". If we have explicit .@ syntax we don't need to force that equivalence.
I would (in brief) define foo.@bar as
Lexically resolve the identifier "bar" and let key be its value.
If key is not a private name object, throw ReferenceError
return foo.[Get]
It would remain the case that using existing semantics
foo.@bar
and
foo[bar]
would evaluate to the same thing. But we don't need to say that or imply this would always be the case if we decided to allow user overrides of [ ] access.