Class syntax enhancements

# Glen Huang (10 years ago)

Since we required methods defined within class definitions non-enumerable by default, I think we should discourage people to do things like:

class Foo { … }
bar = () => {}

Foo.prototype.bar = bar;
Foo.prototype.addtionalMethod = () => {}

Maybe two new syntaxes could be allowed in es7?

class Foo {
	...
	bar: bar
}

partial class Foo {
	addtionalMethod() {}
}

The first allows adding an existing function to a new class. The second allows adding a new method to an existing class. The first syntax probably needs a bit work though, because I see there were some discussions about supporting prototypal properties, and if these properties should be enumerable by default, we probably need different syntaxes for adding properties and methods.

These two should cover the most use cases for Object.defineProperty and Object.defineProperties (which are too verbose to encourage people to use I believe).

What do you think?

# Leon Arnott (10 years ago)

What would specifically be available with this new syntax that couldn't be done with:

class Foo { ... }

Object.assign(Foo, {
  bar, /* Existing method */
  additionalMethod() { ... }, /* New method */
});

apart from, of course, the super binding?

# Glen Huang (10 years ago)

You mean Object.assign to Foo.prototype?

class Foo { ... }

Object.assign(Foo.prototype, {
  bar, /* Existing method */
  additionalMethod() { ... }, /* New method */
});

If I’m not wrong, methods added like this are enumerable.

# Leon Arnott (10 years ago)

Ah, right, my apologies for misreading.

So... I think this scenario would be better served if ES7 had shorthands for non-(enumerability|writability|configurability) added to the object literal syntax, as some in the past have postulated - and a method for copying non-enumerable (and also symbol-keyed) properties was available.

class Foo { ... }

Object.copyOwnProperties(Foo.prototype, {
  noenum bar, /* Assigns 'bar' to the object, making it non-enumerable and non-writable */
  noenum additionalMethod() { ... },
  noenum *[Symbol.iterator]() { ... },
});

Object.assign vs Object.copyOwnProperties may seem awkward, but it's arguably comparable to the Object.keys/Object.getOwnPropertyNames dichotomy - one for the common case, one for the thorough case. (I'm aware that Object.getOwnPropertyNames is deprecated in favour of Reflect.ownKeys, though.)

In cases where all the methods are new, this could be "simplified" to:

Object.copyOwnProperties(Foo.prototype, class {
  baz() { ... }
  *[Symbol.iterator]() { ... }
}.prototype);

Of course, while writing all this I just thought of yet another problem: there's no way to copy accessors using this hypothetical Object.copyOwnProperties.

Maybe there should also be a specially tuned method on Function:

Function.assign(Foo, class {
  qux() {...}
  *[Symbol.iterator] {...}
  get flib() {...}
  static flab() {...}
});

And let copyOwnProperties be used for assigning existing methods.

Classes are kind of an awkward data structure, I must say. :|

# Andrea Giammarchi (10 years ago)

FWIW when I mixin objects (that are considered traits) into classes [1], methods and accessors are assigned as other classes methods/accessors would (non enumerable)

It feels right and work as expected, so your example would simply be


class Foo {
  with: [{
    bar, /* Existing method */
    additionalMethod() { ... }, /* New method */
  }],
  ...
}

all at definition time which is IMO easier to read and follow too.

All other utilities to eventually extend in a different ways could be always used, of course, but I think there should be some imported-into-class behavior provided by mixins or with or traits or however that will be called when it'll be the right time.

Just my 2 cents, Best

[1] WebReflection/es-class/blob/master/FEATURES.md#with ( see Aplication class )

# Glen Huang (10 years ago)

Thanks for the examples. But I wonder why beat around the bush and not let developers express exactly what they want?

# Andrea Giammarchi (10 years ago)

They want magic most of time anyway :-)

I wasn't saying enumerable and others are keywords aren't a bad idea, I'm say when you import to class you would probably expect non enumerable anyway, and too verbose class declarations aren't that good-looking.

Hope you got my point.

# Andri Möll (10 years ago)

Andrea Giammarchi:

They want magic most of time anyway :-)

If the other trend of moving away from monolithic frameworks to composable libraries is any indicator, magic is on it’s way out as well. Good riddance.

Glen Huang:

Since we required methods defined within class definitions non-enumerable by default, I think we should discourage people to do things like:

class Foo { … } bar = () => {} Foo.prototype.bar = bar; Foo.prototype.addtionalMethod = () => {}

Why? It’s the simplest approach for 99 use-cases, disregarding the super issue for a sec. Append to your prototype with regular code, then inherit. Wrap, bind, curry functions to your heart’s content. Simple.

Take it too far and you end up with something akin to Ruby (and its origins): metaclasses of metaclasses, multiple inheritance, prepended modules and appended classes.

Leon Arnott:

So... I think this scenario would be better served if ES7 had shorthands for non-(enumerability|writability|configurability) added to the object literal syntax, as some in the past have postulated - and a method for copying non-enumerable (and also symbol-keyed) properties was available.

Now that’s a better generic proposal. Something for imperative use as well and everything’s covered.

A.

# Domenic Denicola (10 years ago)

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Glen Huang

partial class Foo {

Although I previously thought partial classes were pretty silly because you could just add to the class directly, I can see the appeal given that doing so will involve making your methods non-enumerable plus fixing the super binding (the latter of which is currently impossible in ES6).

We first need to figure out a good desguaring for classes in general into compositional, imperative primitives, like the old toMethod. But once we have that, adding some more sugar on top like partial classes might be reasonable, depending on how ergonomic---or not---those primitives end up being.

# Kagami Rosylight (8 years ago)

I’m not sure it’s okay to reply on this very old thread, but if I’m understanding correctly the desugared code can be fairly short with new Object.getOwnPropertyDescriptors:

function partial(base, extension) {
  extension.prototype.__proto__ = base.prototype.__proto__; // to enable 'super' reference
  const descriptors = Object.getOwnPropertyDescriptors(extension.prototype);
  delete descriptors.constructor; // must not override constructor
  Object.defineProperties(base.prototype, descriptors);
  
  return base;
}
class A {
  foo() {
    return "foo"
  }
}

class B extends A {
  bar() {
    return "bar";
  }
}

partial(B, class {
  foobar() {
    return `${super.foo()}${this.bar()}`;
  }
})

output.value = new B().foobar(); // will be "foobar";

This sample currently only works on Firefox 50+. jsfiddle.net/saschanaz/jcfp53h2

# /#!/JoePea (8 years ago)

Hello Kagami

This works in your simple text case, but I think it may fall apart in more complicated cases. f.e.:

class A {
  foo() {
    return "foo"
 }
}

class B extends A {
}

partial(B, class extends SomeOtherClass {
  foobar() {
    return `${super.foo()}bar`;
  }
})

output.value = new B().foobar(); // will be “foobar;

now the partial function will remove the SomeOtherClass prototype chain and replace it with the chain from B, which would be undesirable (me thinks).

So far, while considering that [super is static](esdiscuss.org topic/the-super-keyword-doesnt-work-as-it-should), the best solution to mixing in other classes is class-factory-style mixins.

But, I think that once Reflect.construct and Proxy is released in Safari 10, it may be possible to write a new multiple-inheritance technique that works with vanilla classes (without requiring class factories). I'm going to try it when Proxies are native in all browsers. It's currently not possible with polyfills, since the behavior can't be polyfilled 100% accurately.

For reference, here's my first two attempts:

  1. gist.github.com/trusktr/05b9c763ac70d7086fe3a08c2c4fb4bf
  2. gist.github.com/trusktr/8c515f7bd7436e09a4baa7a63cd7cc37

Usage would be something like this:

class A extends B {
  // ...
}

class C extends D {
  // ...
}

// extend multiple classes, the one listed first has priority regarding
property lookup:
class E extends multiple(A, C) {
  // ...
}

So far, my implementations work for basic classes, but you'll run into problems in some cases (f.e. differing constructor signatures, and issues with the super keyword).

# Kagami Rosylight (8 years ago)

You’re right, my partial function does not support multiple inheritance and the input class should not have its own prototype chain. I think partial class and multiple inheritance are two different issues, however, as the former can work without the latter and vice versa.