Define static properties and prototype properties with the class syntax
Data properties on the instance prototype are generally considered to be a footgun. If you want to expose a data-like property on the constructor (or the instance prototype), you can use accessors:
let _bar = 1;
class Foo {
static get bar() { return _bar; }
static set bar(value) { _bar = value; }
}
Foo.bar = 43;
I agree data properties on the instance prototype is a bad idea. But static properties are really useful. Could we at lease allow them:
class Foo {
static bar = 43;
}
The case against it is it only works with primitives. If you set an object/array on the prototype it's shared by all instances, which is dangerous.
But allowing getter & setter already makes it dangerous:
let _bar = {}
class Foo {
static get bar() { return _bar; }
}
Objects that have Foo.prototype in the prototype chain can do "this.bar.a = 1", and the change won’t be shadowed.
I found myself looking for a way to define static properties because I want to do this:
function Menu(options) {
this.options = Object.assign({}, Menu.defaults, options);
}
Menu.defaults = { hidden: false };
I want to expose the defaults property so it can be modified by users. This pattern is very ubiquitous in es5. I wonder if the class syntax could allow this pattern to be carried to es6?
You're much better off subclassing menu for that purpose.
class Menu
constructor(options) {
this.options = Object.assign({hidden: false}, options);
}
}
class HiddenMenu extends Menu {
constructor(options){
super(Object.assign({hidden: true}, options));
}
}
Thanks for the sample code.
But the defaults actually contains other properties like width, height, etc that let users customize the constructed menu object. So subclass won’t work here.
A lot jquery plugin are actually written this way:
learn.jquery.com/plugins/advanced-plugin-concepts
Look at the first example, A defaults property is attatched to the constructor.
It's an antipattern to modify things you didn't create, and jQuery is the embodiment of this antipattern :-)
If you really need to do this, you should also put a comment saying "Modify Menu.defaults to change the default settings". And put it in any docs that go with it.
After thinking about it, I think you are right. Defining this defaults property is almost like defining a global variable, that modifying it might affect lots of objects are created with the constructor.
It’s probably a good thing that class syntax doesn’t support it, but I reckon a lot of jquery plugin authors would do class Foo {}; Foo.defaults = {};
nonetheless.
On Sat Dec 13 2014 at 6:04:10 AM Glen Huang <curvedmark at gmail.com> wrote:
After thinking about it, I think you are right. Defining this defaults property is almost like defining a global variable, that modifying it might affect lots of objects are created with the constructor.
It’s probably a good thing that class syntax doesn’t support it, but I reckon a lot of jquery plugin authors would do “class Foo {}; Foo.defaults = {};” nonetheless.
I can't speak for all jQuery plugin authors, but in jQuery Core, static class-side properties (as data and/or functions) are used extensively, eg. api.jquery.com/category/utilities
On Sat Dec 13 2014 at 4:54:55 AM Glen Huang <curvedmark at gmail.com> wrote:
But allowing getter & setter already makes it dangerous:
let _bar = {} class Foo { static get bar() { return _bar; } }
Objects that have Foo.prototype in the prototype chain can do "this.bar.a = 1", and the change won’t be shadowed.
The accessor property "bar" is not created on Foo.prototype, it's created
on Foo itself and would not be found on this
.
In ES5, the example above would look like this:
var _bar = {};
var Foo = function Foo() {}
Object.defineProperty(Foo, "bar", {
get: function() {
return _bar;
}
});
I found myself looking for a way to define static properties because I want to do this:
function Menu(options) { this.options = Object.assign({}, Menu.defaults, options); } Menu.defaults = { hidden: false };
class Menu {
constructor(options) {
this.options = Object.assign({}, Menu.defaults, options);
}
static get defaults() {
return Object.freeze({ hidden: false });
}
}
This has the benefit of preventing unwanted tampering with Menu. defaults
by ensuring that the only way to modify defaults is by explicitly providing
them via new Menu({ my opts })
. The property can be read as
Menu.defaults
but assignment to Menu.defaults
or attempts to change
values or add properties will throw.
I want to expose the defaults property so it can be modified by users. This pattern is very ubiquitous in es5. I wonder if the class syntax could allow this pattern to be carried to es6?
ES6 is done, but this is something that I also want to revisit for ES7 (should be a year following ES6). In the meantime, it's not too painful to address this use case by doing this:
class Menu {
constructor(options) {
this.options = Object.assign({}, Menu.defaults, options);
}
}
Menu.defaults = { hidden: true };
Though I'm not sure what the benefit is to allowing any code access to an the unprotected defaults. If you and I are working on a large system using this class, with potentially many Menu components on the same page and I change the defaults, all other code can no longer trust the defaults to be correct. That said, it's not always as simple as creating a closure with the defaults so they're not exposed at all—exposing them is certainly useful for testing, and/or verifying correct, expected initial state with some baseline shape.
Thank you for the explanation and sample code. I made a mistake there. I was demonstrating that the syntax already allows an object to exist in the prototype chain, so the static keyword really shouldn’t be there.
And you know, I’m still struggling with the idea of an object being in the prototype chain is a bad one or not. I think for people who know what they are doing, it definitely helps. But if the language wants to prevent footgun features for everybody, it seems that it does make sense to make it more limited. So I’m torn right know.
I know you are in the TC39 team (Frankie and Kevin might also be on the team, I’m not sure, would be interested to know if there is a TC39 team member list), and I’d be grateful if you could share your thoughts on this.
The closest I can offer is the attendance record of committee meetings. I keep a record at the beginning of each day of notes here: rwaldron/tc39-notes/tree/master/es6 . It's not 100% complete, of course, as not all members can attend every meeting, but it's close.
as side note, I wonder why static hasn't been considered definable as group/object
class Foo {
static {
bar: 'bar',
get defaults() {
return {de:'faults'};
}
}
}
it would be something like "nothing new to learn" and primitives, as both getters and setters, will be possible, together with public static methods.
It will also make the definition somehow cleaner (one place instead of
potentially many down the class definition) and it will probably avoid
confusion with the this
reference which will be the class itself and not
an instance.
Would any of this make sense for ES7 ?
I like that, can you write up a proposal?
Will do soon
BTW the general idea goes back to Waldemar's ur-JS2/ES4:
Annotated groups are useful to define several items without having to repeat attributes for each one. For example,
class foo { var z:Integer; public var a; private var a; private function f() {} private function g(x:Integer):Boolean {} }
is equivalent to:
class foo { var z:Integer; public var a; private { var b; function f() {} function g(x:Integer):Boolean {} } }
Currently, es6 doesn’t seem to let you define static properties and prototype properties with the class syntax, by that I mean this in es5:
function Foo() {} Foo.bar = 1; Foo.prototype.baz = 2;
Is it possible to allow this in es6 with the class syntax? Or it belongs to es7? Or it’s simply a terrible idea?