Git Product home page Git Product logo

proposal-class-public-fields's Introduction

NOTE: This proposal has been merged with the Private Fields proposal to form a single unified proposal here.

Please do not create new issues/PRs on this repo.

ES Class Fields & Static Properties

This presents two related proposals: "class instance fields" and "class static fields". "Class instance fields" describe properties intended to exist on instances of a class (and may optionally include initializer expressions for said properties). "Class static fields" are declarative properties that exist on the class object itself (and may optionally include initializer expressions for said properties).

Latest spec text: https://tc39.github.io/proposal-class-public-fields/

proposal-class-public-fields's People

Contributors

bakkot avatar danez avatar jeffmo avatar littledan avatar ljharb avatar michaelficarra avatar popenkomaksim avatar varemenos avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

proposal-class-public-fields's Issues

Grammar & computed properties

Does the spec have a grammar specification under development? It seems like the implied grammar, especially with decorators in the mix, has issues that no amount of lookaheads could disambiguate:

class Derp {
    @foo [bar] = baz = derp * derpGen() {}
}

Did the decorator foo [bar] = baz = derp decorate a generator method named derpGen?

Did the decorator foo decorate the property [bar], whose value was baz, and which was followed by a generator method named derpGen?

Did the decorator foo decorate the property [bar], whose value was baz = derp * derpGen(), and which was followed by a syntax error?

This stuff is tricky, and I may just be overlooking something, but it seems as if the grammar might need to either be linebreak sensitive or perhaps mandate semicolons (which would be a little weird, since these aren't statements).

Reflective API for Adding Class Properties

There is an object that represents the class properties that were defined declaratively.

It seems like it is also possible to add new properties for initialization using this API:

Object.defineProperty(
  MyClass[Symbol.classProperties],
  'foo',
  {configurable: true, enumerable: true, writable: true, value: function() { return 123; }}
);

Could you clarify that it is not just for reading but also creating properties?

ES6 classes need fixing

We're adding all this extra syntax just for ES6 classes. Case in point instance and static fields but also decorators.

Decorators won't work with normal functions. But really decorators are nothing more than higher order functions. We shouldn't need new syntax for this stuff.

Instead, using the class fields, we could make the class syntax a little more sane. (It's too late for a re-write)

Problems with ES6 classes

  1. Functions/methods are given special status:
    Javascript has always had first-class functions. In the ES5 days writing classes in JS was clunky, but ES6 classes, methods have become something different than simple values.
    Currently, all methods in a class body get attached to the prototype. But there is no way to attach NON-function values to the prototype anymore.
  2. You can no longer use the word function.

You can't write this:

class X {
  someMethod: function() {... }
}
// OR
class X {
  someMethod = function() {... }
}

With the fields proposal, the second would work but would get attached to each instance, instead of the prototype.

Conclusion

Let me just end here and say ES6 classes have problems, and before we try hacking around it's flaws we should consider making it better.

So perhaps:

class X {
  someMethod = function() {... }
}

should be equivalent to

class X {
  someMethod() {... }
}

And decorators can be applied like so:

class X {
  someMethod = decorate(function() {... })
}

Instead of trying to look more and more like other languages (cough Java cough). Maybe we should try to keep Javascript more internally consistent.

PS:

I also really dislike the requirement of semi-colons at the end of field declarations. Does it really need to be there? I know about the computed names for methods being an edge-case, but surely there's another way around that. Maybe the semi-colons can be optional, just like the rest of Javascript.

Initialized instance fields/properties need to be defined as own properties of the prototype

For more information, see

https://phabricator.babeljs.io/T7301

and referenced other issues

The main problem with the current specification is that it does prevent certain decoration use cases and also that it gives the user some hard time finding bugs when overriding such properties. Besides of that it also imposes additional runtime overhead as such properties will be defined during instancing and not during construction of the class.

In addition, these fields should be made configurable, so that one can decorate and override them or altogether remove them.

And, to be frank, ECMAScript should be an all purpose language and not one that is designed around an idiom that is commonly shared by or that is meant to target a few frameworks.

What this basically means is that the following part of the specification should be revised.

Instance Field Initialization Process

The process for executing a field initializer happens at class construction time. The following describes the process for initializing each class field initializer (intended to run once for each field in the order they are declared):

Let prototype be the prototype object
Let fieldName be the name for the current field (as stored in the slot on the constructor function).
Let fieldInitializerExpression be the thunked initializer expression for the current field (as stored in the slot on the constructor function).
Let initializerResult be the result of evaluating fieldInitializerExpression
Let initializerResult be the result of evaluating fieldInitializerExpression with this equal to instance.
Let propertyDescriptor be PropertyDescriptor{[[Value]]: initializerResult, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}.
Call prototype.defineOwnProperty(fieldName, propertyDescriptor).

do not handle thunking of intiialisation expression using "function wrapper"

A much better way to do it is to capture the ECMAScript code itself as well as the current environment record. Save that, then it can be evaluated later. Additionally, this will clarify that constructor parameters, arguments, and new.target are all not available. Something special will have to be done regarding this for instance properties.

Need userland reflection over class instance fields

There needs to be a way to reflect over the defined instance fields of a class definition. This can likely take the form of something like Function.getOwnFieldDescriptor() and Function.defineField().

Additionally, in order to satisfy needs for some DOM interfaces (at least), there must be some way to lock down a class and prevent addition, removal, or mutation of it's fields. Something along the lines of Function.lockFields().

interaction with super

If you do this:

class B {
  foo = () => 5
}
class S extends B {
  foo() {
    return 7
  }
}

your call to (new S()).foo() will actually return 5 because the assignment in B goes on the instance, thus overriding the S class method.

This is of course as is should be per spec, but it can cause confusion. I wonder if this could be made an error? Since class B will always create the instance variable, class S should not define a class method?

Let and Const

Why are let and const omitted in the syntax? Wouldn't having const be useful for declaring read-only properties?

class MyClass {
  // Ugly
  get myConstA {
    return 13;
  }

  // Requires user-land decorator
  @readonly
  myConstB = 13;

  // Perfect?
  const myConstC = 13;
}

Also, there is a discussion going on about enums. If enums are going to be implemented with enum myEnum = {Red, Green, Blue}; syntax, then it would be hard to put enums in this spec.

Even if enums won't be implemented like that or even they won't be implemented at all, having users explicitly specify let and const should make this spec a lot more future-proof.

Function name inference

I would expect the following to print 'a':

class C {
  a = function(){}
}

print((new C).a.name);

Similarly for static properties and computed names.

Overriding with methods and other edge cases

Under the current proposal, declared property names are [[Set]] during instance initialization. This creates some interesting and perhaps unintuitive edge-cases.

First, if the field is overridden by a method:

class X {
  a = 1;
}

class Y extends X {
  a() { }
}

typeof (new Y).a; // "number"

The definition of a in the base class gets to overwrite the definition of a in the subclass.

Second, if the field is defined as an accessor pair in the base class:

class X {
  get a() { return "abc" }
}

class Y extends X {
  a = 1;
}

(new Y); // throws an error because there is no setter for "a"

It feels like something is going on here that shouldn't. Perhaps fields should not be allowed to shadow methods and vice-versa?

instance property declarations need to be in constructor scope

The proposal as of 9/17 places both instance property declarations and static property declarations inside the class body. For static properties, this is exactly right, as static properties are per-class and are initialized once when the class is initialized.

For instance properties, putting the declaration in the class body is exactly wrong, because instance properties are per-instance precisely in order to vary from one instance to another. The expression used to initialize an instance need to be in the scope of the constructor, as the initial value will often be calculated based on constructor parameters.

The most common response to this objection is that the constructor can re-initialize these properties by assignment. But this confuses initialization with mutation. A declarative property declaration syntax needs to be able to grow into the ability to declare const properties, i.e., non-writable, non-configurable data properties. Whether the ability to declare const properties is provided by syntax or by declaration, their unassignable nature prevents this re-initialization in the constructor.

To make this repair to the proposal, we of course need an alternate concrete syntax proposal. Here are some choices:

Overload the variable declaration syntax

The common ES6 way to give an instance properties is by assignment in the constructor body, such as:

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
}

The first proposal would simply turn these assignments into declarative property initializations by prefixing them with let or const:

class Point {
    constructor(x, y) {
        const this.x = x;
        const this.y = y;
    }
}

This syntax is unambiguous, as it is currently illegal under the ES6 grammar.

Using the const keyword declares the property to be a non-writable, non-configurable data property. Using the let keyword makes a writable, non-configurable data property. Conceivably, using the var keyword would make a writable configurable data property, although there's not much point since assignment already does this. In all cases, these declarations could be decorated.

In a base class as above, perhaps all these declarations must come textually before the first non-declaration use of this, in order to prevent the instance from being observed before it is initialized. In a derived class constructor, perhaps all these declarations must come before the super call, while this is in a temporal dead zone, for the same reason. However, either of these requirements impedes refactoring ES6 code to turn instance assignment into initialization.

Use the reserved "public" keyword

This proposal is like the previous one, but using (and using up) the reserved "public" keyword rather than overloading the meaning of let and const:

class Point {
    constructor(x, y) {
        public this.x = x;
        public this.y = y;
    }
}

or perhaps

class Point {
    constructor(x, y) {
        public x = x;
        public y = y;
    }
}

By default, this makes writable non-configurable data properties. To declare a non-writable property instead, you'd use a decorator.

Since public, unlike let and const, is not already associated with a TDZ concept, the initialization-time considerations above do not have as much force. Nevertheless, observing an uninitialized instance is a hazard that these restrictions can help programmers avoid.

Another possible advantage of public is that private fields might then be declared with the same syntax but using the keyword private.

Use C++-like property initialization syntax

class Point {
    constructor(x, y)
      :x(x), y(y) {
    }
}

Personally I find it ugly. With decorators, they'd be even uglier. I list it because, given precedent, it should at least be mentioned as a candidate.

Computed property name evaluation order

Consider

(class {
  [console.log(1)] = null;
  [console.log(2)](){}
  [console.log(3)] = null;
});

As spec'd, evaluating this class expression would print 2 1 3. This seems clearly wrong; computed property names should be evaluated in the order in which they appear.

Usage with arrow functions

What is the intended behavior here? Based on the location where all of these arrow functions are defined, I think they should all return "outer"

function contextWrapper() {
  const outer = () => this.name

  class Example {
    name = 'instance'
    instanceMethod = () => this.name
    static staticMethod = () => this.name
  }

  const tmp = new Example()

  console.log('outer()', outer())  
  console.log('Example.staticMethod()', Example.staticMethod())
  console.log('tmp.instanceMethod()', tmp.instanceMethod())
}

contextWrapper.call({name: 'outer'})

Using babeljs.io/repl the output is actually:

outer() outer
Example.staticMethod() outer
tmp.instanceMethod() instance

ensure duplicate names are handled properly

class A {
  b = f();
  b = g();
}

This needs to not error defining b on instances of A twice, and needs to evaluate f() and g() each time A is constructed. The value of fresh instances of A should be the return value of g().

Confusion with "this" and computed properties.

What would:

this.foo = 'a';
class Foo {
  foo = 'b';
  [this.foo] = this.foo;
}

produce? An "a" property or a "b" property? An "a" value or a "b" value?

My guess based on my understanding of the spec is that it'd be an "a" property with a "b" value, but I wanted to confirm.

Evaluation order for static properties vs class declaration

Raised over in loganfsmyth/babel-plugin-transform-decorators-legacy#17 but I figured I'd file a proper bug to get a solid answer. What is the expected behavior of

class Foo {
  static me = Foo;
}

with the current spec, it looks like this would be a TDZ error because the Foo property assignment would be evaluated as step 24.v.c.a of ClassDefinitionEvaluation, and the Foo binding does not get initialized until step 26.i.

Babel 6's class property implementation currently evaluates me = Foo after Foo has been initialized, but my transform-decorators-legacy plugin will result in Foo being uninitialized at this point, and the difference between the two can cause trouble for users since code like this will work with standard Babel 6 until they enable decorators, at which point it will break.

Throw on duplicate properties?

For private state, in tc39/proposal-private-fields#22 , there is an issue of a field getting redefined with the same name, and what the semantics are. The resolution there seems to be to throw for duplicate fields. Would this make sense to do for property declarations as well? That would obviate the need for the "this.x = this.x" semantics, as a runtime error would be thrown in the case where it makes sense.

Member on instance vs prototype

TL;DR Don't allow class values without qualifiers. The notion is ambiguous and counter to classes just being syntactic sugar on top of the prototype chain. Options static, let, const would make sense.

Does this proposal cover both ways to set a value?

It can be very useful to set configuration constants on the prototype. They can be accessed in class methods. A nice power is the ability to override these on sub-classes. It can also be used to set a default value more efficiently than in the constructor.

Obviously the way most people are used to is the equivalent of setting the value in the constructor.

given:

class X {
    x = 5
   
    getX() { return this.x; }
}

many people may expect it to be equivalent to,

function X() { this.x = 5; }
X.prototype.getX = function() { return this.x; } 

however that means that the definitions are inconsistent in where they are set, one is on instance, the other is on the prototype. I think a reasonable argument is that the constructor is the place to set instance variables, and the class space is the place for members of the prototype.

function X() { }
X.prototype.x = 5;
X.prototype.getX = function() { return this.x; } 

I think one idea to explore could be to have const denote values set on the prototype and let denote values set on the instance. That would remove the ambiguity of the current notation. It also hints that those on the prototype can be overridden but not changed.

class Foo {
    const  bar = 5
    let baz = 10

    getBar() { return this.bar; }
}

Enumerating through class instance properties prior to construct

Does this proposal allow me to enumerate through the properties of an instance of a class without instantiating it?

Let's say I have a class like this:

class Person {
    firstName = null;
    lastName = null;

    construct() {
        console.log('Hi!');
    }
}

I want to be able to know, without doing new Person() that the Person class has the properties firstName and lastName and what their default values are. The current way that Babel is transpiling this class is like this:

var Person = function () {
    function Person() {
        _classCallCheck(this, Person);

        this.firstName = null;
        this.lastName = null;
    }

    Person.prototype.construct = function construct() {
        console.log('Hi!');
    };

    return Person;
}();

Because the assignments are done in the constructor, I have no insight into what properties will exist on the class until a class instance is instantiated. It would be super useful for the code to be transpiled like this:

var Person = function () {
    function Person() {
        _classCallCheck(this, Person);
    }

    Person.prototype.firstName = null;
    Person.prototype.lastName = null;

    Person.prototype.construct = function construct() {
        console.log('Hi!');
    };

    return Person;
}();

Knowing what properties a class instance has prior to instantiating it is quite useful when writing reflection code.

Order of execution with subclasses

I understand why the current order is the way that it is, but I have a scenario I thought would be supported but currently is not.

Also, is there a people-targetted document which outlines the order? I only established the order from a combination of squinting at the spec and trying examples.

Consider the following example

class Base {
  y = 7;
	constructor(lots, of, args, that, are, a pain, to, repeat, when, defining, children) {
    /**
     Do work using all those constructor args
    **/
  	this.init();
  }
  init() {
    // extension point for subclasses so they don't have to pass along children
  }
}

class Derived extends Base {
  z = 4;
  init() {
    alert(this.z * this.y);
  }
}

var q = new Derived();

The motivation was to avoid having children repeat the constructor signature - and potential errors caused by not passing along arguments correctly. When I attempted to use this approach I hit a snag, the execution order is:

  1. Base class properties
  2. Base class constructor
  3. init()
  4. Derived class properties

Is there any way to place an init routine somewhere after all class properties have been evaluated?

Configurable vs nonconfigurable properties

The current document defines properties that are nonconfigurable, based on a change by @michaelficarra . I wanted to open this bug to discuss whether they should be configurable or nonconfigurable, and lay out the points in both directions.

Pro

  • Nonconfigurability gives us "more guarantees" about the instances that are created (modulo how much flexibility the rest of ECMAScript gives you).
  • Deleting properties creates more hidden classes, so it can be slow (just like creating new properties later); making it an error makes it easier to stay within the efficient code mode.

Con

  • This is somewhat of a divergence from other class-related syntactic constructions, which all define configurable properties.
  • It's not clear (to me) how an optimizing JS implementation could take advantage of nonconfigurability for optimizations, given all the other things going on. Hidden class transitions from deleting a property on an instance are a well-established technique.

Thoughts? @erights @domenic @dherman @allenwb @zenparsing

Helping with progress

Hello.

I'm new to this proposal stuff, but I want to get involved. This proposal, along with one other are especially interesting to me. How can I help move things forward?

Sorry if this isn't the place for these questions. Feel free to lecture me and plaster this issue with links so I can RTFM.

Thanks!

consider using LiteralPropertyName instead of PropertyName

You certainly would not want the name to be evaluated and potentially different for each instance. If the name is only evaluated once, that is a strange inconsistency between how the left and right expressions are evaluated. Additionally, properties like [a] = [0] really look like destructuring bindings, which would be confusing for readers.

Proposal: Add all of the properties at once

This is a cross-cutting proposal, also affecting private state (cc @zenparsing).

What if property declarations/private state fields were added all at once? The semantics would be set up roughly as follows:

  1. Let l be an empty List
  2. For each property declaration/private state f,
  3. If f has an initializer, evaluate it and append the value to l. Otherwise, append undefined
    1. For each property declaration/private state f,
  4. Pop the first element of l and define f to be the value of that element.

The proposal means that later initializers cannot reference earlier initializers in the same class (though subclasses can reference superclasses). One implementation downside would be that if there are many properties/fields, they might be spilled from registers to the stack, but the upside is that the number of observable states is less, which would mean fewer writes to the hidden class slot of objects (especially for interpreters/baseline compilers which would have trouble optimizing it away).

Thoughts? Thanks @verwaest for this suggestion.

[[Set]] vs [[DefineOwnProperty]]

There's some debate over what the semantics should be. I wanted to try to collect arguments in one place.

Set Define
= implies Set. We could use a different sigil (although not :).
People expect to be able to copy code from the constructor to the class body and have it work the same. We don't currently have a syntax-level Define for classes, and we can or should use new syntax to do new things.
Most JS programmers do not distinguish between imperative and declarative position. It is a declarative position. In most cases Set and Define will behave identically, and programmers will have to learn which is which in the remaining cases anyway.
If a class defines a setter for foo to maintain some invariant, some people might be surprised that adding an initializer for foo shadows it instead of triggering it. Also, library authors use setters to deprecate properties (by allowing their use but printing a warning), and subclassers will only get those warnings if initializers use Set. In an object literal, { set foo(x){}, foo: 0 } does not trigger the setter.
Define will allow us to later introduce const properties or types. (And potentially decorators.)
Babel uses Set.

We also have the option of some hybrid strategy: for example, throwing if an initializer would overwrite another property and/or throwing if an initializer would shadow a property from the prototype.

How would decorators work with fields and get/set?

This might be outside the current concerns of this proposal, but I'm wondering how decorators will work alongside fields and get/set.

CanJS has a library can-define we use to add rich behavior to a constructor function and its prototype like this:

var Person = function(){ };
define(Person.prototype,{
  name: {
    value: "Beyonce Knowles",
    set: function(value){
      return value.toUpperCase();
    },
    get: function(value){
      return value.toLowerCase();
    }
  },
  age: {type: "number", age: "35"}
});

var p = new Person();
p.name //-> "beyonce knowles"
p.age //-> 35  (notice it was converted to a number)

value is the initial value of the property. Users expected values to go through the setter, example issue: canjs/can-define#87. I'm trying to figure out how I could migrate this sort of rich behavior to class with decorators, fields, and get/set.

Ideally, something like:

class Person {
  @prop
  name= "Beyonce Knowles";
  set name(newVal){
    return value.toUpperCase();
  },
  get name(newVal){
    return value.toLowerCase();
  }
  @type("number")
  age="35"
}

Would @type get called like:

type( Person.prototype, "age", {value: "35"} )

And be able to provide a get / set definition? Would it get called during the class definition or the construction of the instance?

I doubt it will work this way, but I'd really like @prop to be able to be called with the initial field value:

prop( 
  Person.prototype, 
  "age", 
  {value: "Beyonce Knowles", get: getter, set: setter} )

So I could rewrite the getter/setters to use it if set hasn't been called as follows:

prop = function(prototype, name, definition){
  // create the internal store of data ...
  Object.defineProperty(prototype, "_data", {
    get: function(){
      return this._data = {};
    }
  })
  Object.defineProperty(prototype, name, {
    get: function(){
      // get calls the provided `getter` with the internal data
      return definition.get.call(this, this._data.hasOwnProperty(name) ? this._data[name] :  definition.value );
    },
    set: function(value){
      // set the internal data with the result of the provided `setter`
      this._data[name] = definition.set.call(this, value);
    }
  })
}

Thanks for any clarity you can provide!

Computed properties

Need more description about computed properties. It's a very important thing for class instance fields, case it only way to use Symbol. Ill explain it on usecase in my project.
I'm writing simple orm with java-like declarations, i.e. in Hibernate framework. In this case all fields in data model and links between them are described as instance fields with decorators:

class User extends ActiveRecord{
  id = 0
}

class Group extends ActiveRecord{
  id = 0
}

class Photo extends ActiveRecord{
  @manyToOne(User, 'id')
  userId = null

  @manyToOne(Group, 'id')
  groupId = null

  dateAdd = new Date()
}

What about ActiveRecord class? it needs to store computed metadata about DB schema, about fields, changed by user and needed to save (for example this information can be collected by proxy object). But i dont want to show this metadata by toString() or JSON.stringify() methods. I want to show user only its own data. So ill store this metadata in Symbol, and this metadata needed for each model instance. So ActiveRecord needs to be something like this:

class ActiveRecord{
  [framework.symbols.modelMetadata] = {
    ...
  }
}

As instance fields is declarative way to describe fields, they will be often used with class decorators in libraries and frameworks, so many people will used them with symbols to hide frameworks metadata. I request better description with using instance fields with computed properties.

Automatically adding a new public API member [Symbol.classProperties] to every class that uses a given syntax is not good

This makes this proposal unusable for libraries which need to conform to specified API contracts, for example jsdom.

It's also very strange that using syntax in an otherwise-normal class suddenly introduces a new member on that class that you didn't declare.

A realistic alternative would be some kind of reflective API that is not installed as a member on the class and on its prototype. Although the motivation for this entire reflective API story is lacking, being only a couple sentences in the current docs.

'this' in static initializers

If we stick with the model last settled on of initializer bodies being scoped as methods, and do the same for static fields, then classes would be accessible while they were being constructed via this. We have a TDZ for class names specifically to avoid this (it previously only arose for computed method names); I believe this should likewise have a TDZ, being bound at the same time as the class name.

Thus the following code would be an error:

class C {
  static a = this;
}

and this would be legal:

class C {
  static a = () => this;
}
C.a(); /// === C

Thanks to @ajklein for pointing this out.

(NB: The spec text as it currently stands has no special scoping applied to static initializers, so this refers to whatever it would outside of the class, but I believe that doesn't reflect the last consensus of the committee.)

SuperProperty references in field initializers

Currently we do not have any explicitly notated support for evaluating SuperProperty references in field initializers. Given that this is available, it seems it would only be natural to also provide SuperProperty as well.

Syntactic ideas to try to clarify the different execution time and scoping

At the March TC39 meeting, I and others were able to articulate our misgivings about the current proposal. The obviously troublesome cases are things like:

class C {
  [this.bar] = this.bar;
  [baz] = baz;
}

where the left-hand side executes at a completely different time, and in a completely different scope, than the right-hand side. Myself in particular find the idea of two sides of an = sign having different bindings to be just too strange.

I think this could be helped with syntactic work to make it clear that the right-hand side is a "thunk" executing later and in a different scope. Here are some strawmen ideas:

  [baz] := baz // at least makes it clear this is not straightforward =
  [baz] := { return baz; } // very clear this is a thunk
  // or allow both, i.e. use arrow function body-esque rules?

  [baz] := do { baz; } // use do expressions as the thunk (allows omitting return)
  [baz] do= { baz; } // !?!?

  // or any of the above using different sigils, e.g.:
  [baz] <- { return baz; }
  [baz] <- { baz; }

I think this is particularly important in some of the common use cases, e.g. "method binding":

class C {
  method = (foo) => bar;
}

I have seen people do this just because they like arrow functions and want the concise body, not realizing that this is a significant semantic shift: removing the prototype property, and creating a new function instance every time a C is constructed. People's mental model, in other words, is that this is only evaluated once.

Contrast:

class C {
  method := { return (foo) => bar; }
}

Here it should be intuitively clear from looking at the code that there's a bit of code that will run, creating and returning a new arrow function every time. That's hugely beneficial.

Semi-colons required?

Are semi-colons required per the syntax?

class C {
  static prop = 'a';
}
// or is this ok too?
class C {
  static prop = 'a'
}

The reason I ask is that up until today, I had been omitting the semi-colon. I write without semi-colons to begin with, but it seemed to make complete sense since classes don't use semi-colons or commas for its regular syntax. However, I started using Flow today with React and it keeps erroring out saying there is a syntax error. It goes away if I use the semi-colon like first part in the example above.

If the syntax requires it, than I am ok with that I guess. But it does seem out of place and I don't know why you would need the semi-colon.

about react use case

In React es6 classes, we want autoBinding, we can do:

class Foo extends React.Component {
  render() {
    // ...
  }
  handleChange = (e) => {
    // ...
  }
}

But handleChange is instance field, how we can defined prototype field in class.

use consistent semicolon elision rules

my JS style elides semicolons, and method literals in class declarations have no trailing delimiter neither.

therefore requiring semicolons results in a idiosyncracy compared to the rest of the language’s grammar.

i’m aware of #25 which was about a quick fix for ambiguities by requiring them temporarily. this bug is about the backwardds-compatible idiomatic final grammar which will allow eliding semicolons.

Updated status:

this was a misunderstanding. @jeffmo explained it here.

ASI is in effect here, and babel won’t require semicolons anymore soon

calling subclass property initializers in the constructor of a superclass

I'm working on a React framework suitable for my projects. It would be great if I could set this.state = this.getState(); on the constructor of the Page superclass, but it does not seem to work. Is this intended behaviour of the property initializers proposal, or am I missing something?

export default class BankTransactionPage extends Page {

    constructor(props) {
        super(props);
    }

    getState = () => {
        return {
            bankTransaction : BankTransactionRepository.getById(this.props.bankTransactionId)
        };
    }

    componentWillLoad = (props) => {
        const bankTransactionId = props.params.bankTransactionId;

        Command.execute(new LoadBankTransactionPageCommand(bankTransactionId));
    }

    // ...
}

export default class Page extends React.Component {
    constructor(props) {
        super(props);

        // Bummer, this.getState seems undefined...
        this.state = this.getState();
    }

    componentDidMount() {
        this.componentWillLoad(this.props);

        Command.addListener(this);
    }

    componentWillReceiveProps(props) {
        this.componentWillLoad(props);
    }

    componentWillUnmount() {
        Command.removeListener(this);
    }

    processEvent = (event) => {
        this.setState(this.getState());
    }
}

Uninitialized properties should still exist

First of all, I hope that opening an issue here is the correct starting point for suggesting a change to the proposal. @loganfsmyth (one of the Babel maintainers) suggested that I open a new issue, since my suggestion is kind of independent of this issue that I commented on. *

Currently, the proposal specifies that properties that aren't given default values should be completely ignored, i.e. this:

class Demo {
	foo;
}

translates to this:

function Demo() {
    //no mention of foo property whatsoever
}

Beyond the fact that one would expect the runtime environment to be aware of the property even if it hasn't been initialized yet, this is problematic because you can't enumerate over the property or check for its existence in any way before it is given a value:

let d = new Demo();
('d' in foo); //false

As I explained here, this makes some common and useful patterns impossible to implement using class properties.

I propose changing the spec such that the above example would transpile to this:

var Demo = function Demo() {
  ...
  this.foo = undefined;
};

Or the more spec-conformant version:

var Demo = function Demo() {
  ...
  Object.defineProperty(this, "foo", {
    enumerable: true,
    writable: true,
    value: undefined
  });
};

Logan pointed out here that this would mean that uninitialized properties in subclasses would be set to undefined even if the parent class specified a default value, for example:

class Base {
    foo = 4;
}
class Child extends Base {
    foo;
}

let c = new Child();
c.foo; //undefined

After considering it and comparing with the behavior of Java and PHP, I'm thinking that the above behavior is how it should work, even if it's initially surprising to some users. As I said in my reply to Logan:

If I'm explicitly declaring a property in a subclass, I don't think I'd want the addition of a default value for that property in the parent class to change the behavior of the child class -- especially if the parent class is maintained by a 3rd party.

However, I think this is a topic that should be discussed to weigh the pros and cons. If it's decided that uninitialized properties shouldn't overwrite the default value specified in a parent class, then the implementation could be modified accordingly, for example:

function Child() {
    Base.apply(this, arguments);
    this.foo = ('foo' in this) ? this.foo: undefined;
}

Or more succinctly:

function Child() {
    Base.apply(this, arguments);
    this.foo = this.foo;
}

(Technically this alteration would only be needed for subclasses, but perhaps it would be simpler to be consistent.)

I look forward to hearing people's thoughts on this - thank you.


* But it's still related, and I think it would be ideal for instance properties to exist on the prototype somehow (as long as non-primitive default values are defined in the constructor rather than the proprotype).

Splitting this proposal into two

Do you mind to split this proposal into two different, e.g. class fields and static properties? AFAIK there is nothing wrong about static properties so no reasons for them to be blocked by other one at stage 1 and it can move to stage 2 or even stage 3.

extend to get/set methods?

I've been happily trying this proposal through babel, and appreciate the convenience it adds for the purpose of assigning methods -- unlike the traditional way of adding class methods, this allows using wrapper functions around methods more easily (i.e. without like moving the assignments into the constructor).

As it stands, it does not appear possible to similarly assign set/get methods though. I'd be happy to be able to see this become available for that purpose as well. To me this feels like a natural extension to the functionality provided by the current proposal, though I don't currently see anything related to this mentioned, I imagine likely because intent has been different.

This may be outside of scope here, and perhaps Yehuda Katz's decorators proposal may be seen as having overlap in the sense of allowing one to decorate methods. In any case, I'd be glad to leave this here for further discussion.

Proposal clarification

I was introduced to this proposal from a Babel issue on supporting computed class properties. Is that the gist of this proposal? If it is I think it could use a mention of computed properties in its description or spec text because at first glance that was hard to pick out.

Could we do something about *this* ?

Could we do about the repeated use of _this_ keyword in class centric Ecmascript code?

Looking at any class centric ES2015 or typescript you will see _this_ being used a lot. IMO this is a problem because it harms the readability and increases the file size. A colleague of mine's reaction over our typescript code was "Somebody has to do something about this" and I agree. The essence of javascript has been small, readable and unbloated code. And we all know the success of that.

Couldn't this proposal also define that closure lookups should include class property checking?

class Person {
    firstName;
    lastName;
    age;

    constructor (firstName, lastName, age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    hello() {
        return `Hello ${firstName} ${lastName}!`;
    }

    birthday() {
        ++age;
    }
}

Execution order during initialization

class ModelBase{
  constructor(initParams){
    for (let i in initParams) {
      this[i] = initParams[i];
    }
  }
}

class MyModel extends ModelBase{
  a = null;
  b = 'foo';
  c = 123;
  constructor(initParams){
    super(initParams);
  }
}

let m = new MyModel({b: 'bar', d: 0});

if we are applying all instance properties AFTER super: m == {a: null, b: 'foo', c: 123, d: 0} - hard to use in common usecases with inheritance
if we are applying all instance properties BEFORE super: m == {a: null, b: 'bar', c: 123, d: 0} - all as expected.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.