Giter Site home page Giter Site logo

tc39 / proposal-class-fields Goto Github PK

View Code? Open in Web Editor NEW
1.7K 111.0 113.0 529 KB

Orthogonally-informed combination of public and private fields proposals

Home Page: https://arai-a.github.io/ecma262-compare/?pr=1668

HTML 93.78% Shell 0.08% JavaScript 4.34% CSS 1.81%

proposal-class-fields's Introduction

Class field declarations for JavaScript

Daniel Ehrenberg, Jeff Morrison

Stage 4

A guiding example: Custom elements with classes

To define a counter widget which increments when clicked, you can define the following with ES2015:

class Counter extends HTMLElement {
  clicked() {
    this.x++;
    window.requestAnimationFrame(this.render.bind(this));
  }

  constructor() {
    super();
    this.onclick = this.clicked.bind(this);
    this.x = 0;
  }

  connectedCallback() { this.render(); }

  render() {
    this.textContent = this.x.toString();
  }
}
window.customElements.define('num-counter', Counter);

Field declarations

With the ESnext field declarations proposal, the above example can be written as

class Counter extends HTMLElement {
  x = 0;

  clicked() {
    this.x++;
    window.requestAnimationFrame(this.render.bind(this));
  }

  constructor() {
    super();
    this.onclick = this.clicked.bind(this);
  }

  connectedCallback() { this.render(); }

  render() {
    this.textContent = this.x.toString();
  }
}
window.customElements.define('num-counter', Counter);

In the above example, you can see a field declared with the syntax x = 0. You can also declare a field without an initializer as x. By declaring fields up-front, class definitions become more self-documenting; instances go through fewer state transitions, as declared fields are always present.

Private fields

The above example has some implementation details exposed to the world that might be better kept internal. Using ESnext private fields and methods, the definition can be refined to:

class Counter extends HTMLElement {
  #x = 0;

  clicked() {
    this.#x++;
    window.requestAnimationFrame(this.render.bind(this));
  }

  constructor() {
    super();
    this.onclick = this.clicked.bind(this);
  }

  connectedCallback() { this.render(); }

  render() {
    this.textContent = this.#x.toString();
  }
}
window.customElements.define('num-counter', Counter);

To make fields private, just give them a name starting with #.

By defining things which are not visible outside of the class, ESnext provides stronger encapsulation, ensuring that your classes' users don't accidentally trip themselves up by depending on internals, which may change version to version.

Note that ESnext provides private fields only as declared up-front in a field declaration; private fields cannot be created later, ad-hoc, through assigning to them, the way that normal properties can.

Major design points

Public fields created with Object.defineProperty

A public field declarations define fields on instances with the internals of Object.defineProperty (which we refer to in TC39 jargon as [[Define]] semantics), rather than with this.field = value; (referred to as [[Set]] semantics). Here's an example of the impact:

class A {
  set x(value) { console.log(value); }
}
class B extends A {
  x = 1;
}

With the adopted semantics, new B() will result in an object which has a property x with the value 1, and nothing will be written to the console. With the alternate [[Set]] semantics, 1 would be written to the console, and attempts to access the property would lead to a TypeError (because the getter is missing).

The choice between [[Set]] and [[Define]] is a design decision contrasting different kinds of expectations of behavior: Expectations that the field will be created as a data property regardless of what the superclass contains, vs expectations that the setter would be called. Following a lengthy discussion, TC39 settled on [[Define]] semantics, finding that it's important to preserve the first expectation.

The decision to base public field semantics on Object.defineProperty was based on extensive discussion within TC39 and consultation with the developer community. Unfortunately, the community was rather split, while TC39 came down rather strongly on the side of Object.defineProperty.

As a mitigation, the decorators proposal provides the tools to write a decorator to make a public field declaration use [[Set]] semantics. Even if you disagree with the default, the other option is available. (This would be the case regardless of which default TC39 chose.)

Public fields are shipping in Chrome 72 with [[Define]] semantics, and this decision on semantics is unlikely to be revisited.

Fields without initializers are set to undefined

Both public and private field declarations create a field in the instance, whether or not there's an initializer present. If there's no initializer, the field is set to undefined. This differs a bit from certain transpiler implementations, which would just entirely ignore a field declaration which has no initializer.

For example, in the following example, new D would result in an object whose y property is undefined, not 1.

class C {
  y = 1;
}
class D extends C {
  y;
}

The semantics of setting fields without initializers to undefined as opposed to erasing them is that field declarations give a reliable basis to ensure that properties are present on objects that are created. This helps programmers keep objects in the same general state, which can make it easy to reason about and, sometimes, more optimizable in implementations.

Private syntax

Private fields are based on syntax using a #, both when declaring a field and when accessing it.

class X {
  #foo;
  method() {
    console.log(this.#foo)
  }
}

This syntax tries to be both terse and intuitive, although it's rather different from other programming languages. See the private syntax FAQ for discussion of alternatives considered and the constraints that led to this syntax.

There are no private computed property names: #foo is a private identifier, and #[foo] is a syntax error.

No backdoor to access private

Private fields provide a strong encapsulation boundary: It's impossible to access the private field from outside of the class, unless there is some explicit code to expose it (for example, providing a getter). This differs from JavaScript properties, which support various kinds of reflection and metaprogramming, and is instead analogous to mechanisms like closures and WeakMap, which don't provide access to their internals. See these FAQ entries for more information on the motivation for this decision.

Some mitigations which make it easier to access

  • Implementations' developer tools may provide access to private fields (V8 issue).
  • The decorators proposal gives tools for easy-to-use and controlled access to private fields.

Execution of initializer expressions

Public and private fields are each added to the instance in the order of their declarations, while the constructor is running. The initializer is newly evaluated for each class instance. Fields are added to the instance right after the initializer runs, and before evaluating the following initializer.

Scope: The instance under construction is in scope as the this value inside the initializer expression. new.target is undefined, as in methods. References to arguments are an early error. Super method calls super.method() are available within initializers, but super constructor calls super() are a syntax error. await and yield are unavailable in initializers, even if the class is declared inside an async function/generator.

When field initializers are evaluated and fields are added to instances:

  • Base class: At the beginning of the constructor execution, even before parameter destructuring.
  • Derived class: Right after super() returns. (The flexibility in how super() can be called has led many implementations to make a separate invisible initialize() method for this case.)

If super() is not called in a derived class, and instead some other public and private fields are not added to the instance, and initializers are not evaluated. For base classes, initializers are always evaluated, even if the constructor ends up returning something else. The new.initialize proposal would add a way to programmatically add fields to an instance which doesn't come from super()/the this value in the base class.

Specification

See the draft specification for full details.

Status

Consensus in TC39

This proposal reached Stage 3 in July 2017. Since that time, there has been extensive thought and lengthy discussion about various alternatives, including:

In considering each proposal, TC39 delegates looked deeply into the motivation, JS developer feedback, and the implications on the future of the language design. In the end, this thought process and continued community engagement led to renewed consensus on the proposal in this repository. Based on that consensus, implementations are moving forward on this proposal.

Development history

This document proposes a combined vision for public fields and private fields, drawing on the earlier Orthogonal Classes and Class Evaluation Order proposals. It is written to be forward-compatible with the introduction of private methods and decorators, whose integration is explained in the unified class features proposal. Methods and accessors are defined in a follow-on proposal.

This proposal has been developed in this GitHub repository as well as in presentations and discussions in TC39 meetings. See the past presentations and discussion notes below.

Date Slides Notes
July 2016 Private State πŸ“
January 2017 Public and private class fields: Where we are and next steps πŸ“
May 2017 Class Fields Integrated Proposal πŸ“
July 2017 Unified Class Features: A vision of orthogonality πŸ“
September 2017 Class fields status update πŸ“
November 2017 Class fields, static and private πŸ“
November 2017 Class features proposals: Instance features to stage 3 πŸ“
November 2017 ASI in class field declarations πŸ“
May 2018 Class fields: Stage 3 status update πŸ“
September 2018 Class fields and private methods: Stage 3 update πŸ“
January 2019 Private fields and methods refresher πŸ“

Implementations

You can experiment with the class fields proposal using the following implementations:

  • Babel 7.0+
  • Node 12
  • Chrome/V8
    • Public fields are enabled in Chrome 72 / V8 7.2
    • Private fields are enabled in Chrome 74 / V8 7.4
  • Firefox/SpiderMonkey
    • Public instance fields are enabled in Firefox 69
    • Public static fields are enabled in Firefox 75
  • Safari/JSC
    • Public instance fields are enabled in Safari 14
    • Public static fields are enabled in Safari Technology Preview 117
    • Private fields are enabled in Safari Technology Preview 117
  • Moddable XS
  • QuickJS
  • TypeScript 3.8

Further implementations are on the way:

Activity welcome in this repository

You are encouraged to file issues and PRs this repository to

  • Ask questions about the proposal, how the syntax works, what the semantics mean, etc.
  • Discuss implementation and testing experience, and issues that arise out of that process.
  • Develop improved documentation, sample code, and other ways to introduce programmers at all levels to this feature.

If you have any additional ideas on how to improve JavaScript, see ecma262's CONTRIBUTING.md for how to get involved.

proposal-class-fields's People

Contributors

anba avatar b-fuze avatar bakkot avatar caiolima avatar chicoxyzzy avatar davismj avatar exarus avatar exe-boss avatar feysalikbal avatar jason-cooke avatar jbruni avatar jeffmo avatar jesstelford avatar jlhwung avatar jridgewell avatar jugglinmike avatar koba04 avatar legendecas avatar littledan avatar ljharb avatar ms2ger avatar mweststrate avatar qubyte avatar robpalme avatar rwaldron avatar tjcrowder avatar tombyrer 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  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

proposal-class-fields's Issues

Initializer Containing arrow containing `arguments`

The rule which prevents initializers from referring to arguments uses Contains, which does not descend into arrow functions except for new.target, super, and this.

It should be an early error, I think. Including recursively - we should disallow

class A {
  a = b => c => { return arguments; };
}

Not entirely sure how to spec it though.

Typo in DefineFields

Small typo in DefineFields -- initialValue should be initValue

Current:

8. If fieldName is a Private Name,
    a. Perform ? PrivateFieldAdd(fieldName, receiver, initialValue).

Should be:

8. If fieldName is a Private Name,
    a. Perform ? PrivateFieldAdd(fieldName, receiver, initValue).

Proposal: keyword to replace `#` sigil

(I have edited this description from the original in response to feedback; original is here)

In https://github.com/tc39/proposal-private-fields, I see a redirect to this repo for future discussion and current status, as well as this:

Q: Can we reconsider, in the syntax, the decision to do...
A: Yes, it's not too late.

There seems to be general dissatisfaction with the # sigil for private fields. I would like to propose an alternate syntax with the same semantics:

Use a private unary prefix keyword to access a "private this", such that private this.foo is identical to the currently proposed #foo.

class Point {
  
    private x;
    private y;

    constructor(x = 0, y = 0) {
        private this.x = +x;
        private this.y = +y;
    }

    get x() { return private this.x }
    set x(value) { private this.x = +value }

    get y() { return private this.y }
    set y(value) { private this.y = +value }

    equals(p) { return private this.x === private p.x && y === private p.y }

    toString() { return `Point<${ private this.x },${ private this.y }>` }

}

Other possibilities for the keyword include:

Advantages:

  • private is already reserved, and would have no other reasonable use.
  • # is the only "free sigil" left on a US keyboard for JavaScript (that I know of) and might be better saved for a feature which would be used in both application and library code, rather than only library code.
  • It is immediately clear what private. is for; # is not likely to be immediately clear.
  • It would be friendlier to existing language tooling and implementations (eg; syntax highlighters, static analysis tools, and parsers would require minimal/no modification).
  • It is more obvious that private this.x and this.x are completely separate, and can peacefully coexist as unrelated fields.
  • It resolves the surprising difference between this.#x and this['#x'].

Downsides:

  • private this.foo would likely be surprising to a Java or C# engineer, where private is used differently. (I would argue this is similar to the existing self/this differences).
  • private is already implemented in TypeScript, and this would clash. (I would argue TypeScript would likely be able to adapt).

(original proposal was private.foo instead of private this.foo)

Destructuring instances with private fields

It just occurred to me that, due to the nature of private-field names, destructuring private fields off an instance into a local binding is not possible with the spec as-is.

I think this could be fixed with a simple tweak to destructuring grammar, though:

const {#pField: pField} = this;

would roughly desguar to

const pField = this.#pField;

Should we include this destructuring grammar in this proposal? Or defer it to a separate proposal?

(I would be in favor of including it if it's as non-controversial as I imagine, defer if it's at all controversial)

Editorial: Mismatch in specificity between private fields and properties?

The spec text about how properties are represented is a bit high-level, with wording like "Let X be O's own property whose key is P." Private state, on the other hand, is an explicit List of Records, where one slot in the record is the private name, and the other is the value (this may change to a property descriptor, to take methods and decorators into account). I don't see a good reason why these should be different. Should one of them be more formal, or the other more informal?

cc @bterlson @allenwb @anba @erights @bakkot

Adoption timeframe for major browsers?

I'm a huge fan of this proposal, makes for very clean class structures.

I've been watching for browsers to start adopting this feature behind a feature flag for a long time:

http://kangax.github.io/compat-table/esnext/

Currently no major browser has the feature implemented behind a flag. I'm currently using Babel to enable me to use the feature. I'm curious to know if anyone knows when Chrome and the others would start working on this one and how I can help.

Field named "constructor"

What is supposed to happen when a field with the name "constructor" is present? The property seems to be silently dropped.

Furthermore, this area of the proposal is currently ill-formed. PropName is applied to things on which it's not well-defined.

Mostly private behaviour possible anyway

Mostly private behaviour has been possible with standard variables var in functions for a long time anyway.

Perhaps this.{var} could retain public behaviour and private vars could just be prefixed with var, or like python prefixing with underscore _{var} could signify you are not meant to access.

I use the term mostly private, because it's non-trivial to access, and requires a fair bit of effort to get to "mostly private" variables anyway.

Use both the # sigil and keywords

For the record, I find the syntax of this proposal to be awful and hope it's rejected in its current form. However, considering this seems likely to be pushed through regardless of overwhelming feedback, I'd like to suggest an amendment to make it slightly more palatable.

class Example {
  private #field1;
  protected #field2;
  public field3, field4 = true, field5;

  private field6; // Error: private field names MUST begin with a hash
  protected field7; // Error: protected field names MUST begin with a hash
  public #field8; // Error: public field names MUST NOT begin with a hash

  constructor(field1, field2, field3, field5) {
    this.#field1 = field1;
    this.#field2 = field2;
    this.field3 = field3;
    this.field5 = field5;
  }

  ...
}

Basically: keep the hash to distinguish between public and non-public property names but use keywords before their declaration. Yes, this is slightly more verbose. However, the code's effect is significantly more clear to the reader. At worst, a newcomer might wonder why the author chose to name all their non-public variables with a leading hash, instead of currently mistaking it for a comment line. It's also alot more flexible; how does the current proposal allow for future extensions such as protected? This extensibility is imo a key advantage.

(Ideally, this principle would be applied but with an underscore instead of a hash. I've read the FAQ but the section regarding runtime checks on the receiver is completely unsubstantiated, and the section below that regarding lack of static types is nonsense [eg PHP manages just fine]. But I digress...)

Typo in ClassElementEvaluation

It seems like:

2.3 Static Semantics: ClassElementEvaluation

should actually be:

2.3 Runtime Semantics: ClassElementEvaluation

:)

Expected behaviour question

class Foo {
  constructor(opts = {}) {
    for (const key in opts) {
      this[key] = opts[key];
    }
  }
}

class Bar extends Foo {
  baz = "bat";

  constructor(opts = {}) {
    super(opts);
  }
}

const bar = new Bar({ baz: "quux" });
const bazValue = bar.baz; // what's the expected value of this? "quux" or "bat"? 

Local class values

I do not know if this was discussed in the context of the current proposal for class fields, I would like to ask if it would be acceptable a syntax like this together with or instead of private fields:

class SomeClass {

  const localConst = 0;
  let localLetVariable = 1;
  var localVarVariable = 2;

  publicField = 3;
  static staticPublicField = 4;

  someMethod() {
    console.log(localConst, localLetVariable, localVarVariable,
      this.publicField, SomeClass.staticPublicField);
  }
}

The local values declaration might look a bit ambiguous, but from my point of view it is a more "JS" way to store private data.

Factory class

I am trying to understand what is the advantage of this standard over using a factory 'class' with private methods properties in the closure.

There are many possible such factory solution. As an example I have been using a very simple one I wrote a while ago, see - https://github.com/kofifus/New

Once you get over the fact that I added a method to Function prototype, and that private methods are redefined in every instance (which is optimized by all modern JITs as to be a mostly negligent overhead), I hope you can see how such a solution provides a very clear and straight forward implementation of private methods and properties.

I am not writing this to publicize my solution. As I said above there are many, probably better ways to achieve this. My point is that private fields can be achieved cleanly and easily without changing the language once you stop trying to patch the IMO quite lame ES6 class construct.

Thx!

Inherited Static properties

What is the output of:

class Base {
  static #field = 'hello';

  static get() {
    return this.#field;
  }
}

class Sub extends Base {}

// This one isn't controversial
Base.get() // => 'hello'

// But what does this do?
Sub.get()

I want it to equal 'hello', but I can't find where the behavior is defined. It's likely just because I'm not familiar enough with the spec text.

Maybe it's an error, though? Since I don't think Sub has #field in its [[PrivateFieldValues]] list, unless there's some super class iteration I'm not seeing.

[Open discussion] What would be for me the perfect class in js

Hello everybody, after following the private class fied proposal and now this proposal, I wanted to discuss : why we don't go further.

First I need to specify i'm not a java, C++ guy or whatever, but a really involved an ecmascript lover. My motivation is to reach a consistent structure across languages heavily used and which have already implemented all the best for classes (since decades).

So for me what would be the perfect class in ecmascript :

1) attribute/method modifiers

In most of the language we find : public, private, protected and static. Currently only static is supported. For me we should use all of this words (already implemented in many languages) to keep consistency and fast code adaptation from one language to another.

The # for private sound wrong for me and the discussion (tc39/proposal-private-fields#14) didn't convince me (people proposed concrete solutions to every problems...). Moreover the protected is still missing but extremely used in inheritance (sound strongly necessary).

Because we love to have full control of class properties in ecmascript, we could add a new attribute to descriptor when using Object.getOwnPropertyDescriptor or Object.defineProperty(https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/getOwnPropertyDescriptor) :

interface Descriptor {
  value:? any;
  writable:? boolean;
  ...
  modifiers: ('static' | 'public' | 'protected' | 'private')[]
}

The new modifiers attribute could be an array of string representing the access of the method, and could potentially be modified after initialisation (to allow the same power than Reflect provides, allow a "backdoor" on external libraries, and allow descriptor to modify modifiers). This would bring far more power than the actual #field propose.

The public, private, protected should be allowed before static too.


To allow external classes or function to access to a protected or private member, we could use the friend keyword. Something similar to

class A {
  friend B
  private attr = 3;
}

This means than the class B can access the attribute attr of A even if it's private.
We could then extends the Object.defineProperty :

interface Descriptor {
  ...
  friends: any[]
}

friends could be an array of friend functions or classes.


Finally for modifiers, a const keyword could be used to specify constant attributes, only allowed to be initialized into the constructor.

class A {
  private const attr = 3;
}

This will be a shortcut for writable=false

2) Multiple inheritance

Multiple inheritance is something that a lot of developers wants (there is a lot of subject or tutorial on the web) but can only be archived through mixins or factories. I would enjoy that you won't reply : "this is too complex because of diamond structures" or whatever because this is obviously FALSE (other languages like C++ archive it easily).

The following is just an idea how to bring multiple inheritance, it's not a concrete proposal.

First of all, because of the javascript current inheritance system with prototype, we can only inherit from one class. No more. A.prototype = Object.create(B.prototype); or in es6 class A extends B.

So we should introduce some king of new syntax.

  1. We could use for example a new attribute prototypes which would be an array of mother classes, and prototype would point on the first element of this list ensuring retro-compatibility.

A.prototypes = [Object.create(B.prototype), Object.create(C.prototype)];
A extends B, C

  1. instanceof should then search for all subclasses, so new A() instanceof C would return true.

  2. The super keyword will need some adjustments:
    I propose this king of syntax super<B>.method : the super class here is B.
    To init a class:

constructor() {
  super<B>(...params);
  super<C>(...params);
}

Or we could use some C like : super::B.
Using super.method will call the first super class (here B) for retro-compatibility.

Some other fancy stuff could be added too :

  • supers keyword to init many classes at once : supers(params class B, params class C), ...
  • some kind of Reflect.super(methodName[, superClass]) to allow apply / call on the super<B>().

3) Abstract classes

Not the most important but really enjoyable, the abstract keyword could be use before the class keyword to define abstract classes. An abstract class may have abstract members and can't be initialized.

This still need more specifications.


So, the discussion is open. My purpose it to bring more in a big step to ecmascript instead of doing small steps and be stuck with retro-compatibility because of too various incremental specifications.

Derived class access

Firstly, I don't want to rehash the "private" keyword argument discussion as that is happening elsewhere.

One thing that is not clear to me is the interaction between a class and the parent class that it is derived from. Could we get some examples of how private/public fields work (or not work) in the scenarios below?

class Shape {  
    // private field not accessible to any class that extends this
    // in C# land I would make this private.
    #id;

    // private field accessible to any class that extends this
    // in C# land I would make this protected
    #origin;

    // public readonly field
    // in C# land I would use the readonly keyword
    sides;

    // public field
    tag;

    constructor (id, x, y) {
        this.#id = id;
        this.#origin = new Point(x, y);
        this.sides = 0;
        this.tag = null;
    }
    equals (other) {
        return other.#id === #id;
    }
}
class Rectangle extends Shape {
    constructor (id, x, y, width, height) {
        super(id, x, y);
        this.sides = 4;
    }

    tests () {
        expect(this.#id).to.beUndefined();
        expect(this.#origin).to.beDefined();
        expect(this.sides).to.be(4);
        expect(this.tag).to.be(null);
    }
}

var r = new Rectangle("abc", 1, 2, 3, 4);
r.tag = 1234;

Move to user/library space with decorators?

I wonder if it would make sense to have class access modifiers be a userspace feature via decorators rather than a defined syntax.

In way, access modifiers really are a meta programming construct. So it would sort of make sense to use the upcoming decorator syntax for implementation, also it wouldn't limit class access to "whack a mole" additions so that JS can be like other languages. Currently we're talking about private and public, but how long will it be until there's pressure for 'protected'? After that, how long until something like c++ 'friend' is proposed? If access modifiers are handled via decorators instead, this becomes a library issue.

So, '@private', '@protected', etc... would do what you expect, and might be added to the core-decorator library.

Additionally, I could see fancy new concepts emerging for wild access modifiers like @class_signed_by("[public key]") that might limit access to a class written by an authorized developer.

In order to make the decorator concept work, I think there's a need to tighten up Function.caller a bit, or resurrect arguments.caller - they'd have to be expanded a bit to include the context of the calling function, or perhaps an addition like Function.class. Are there security concerns with this?

Remove comma-separated field declaration syntax from this proposal

Comma-separated field declarations were added relatively recently. Let's split out comma-separated field declarations into a separate proposal to be considered with more time to think over some issues.

Comma-separated field declarations have led to a few sources of confusion, including:

  • In conjunction with decorators, should the decorator apply to only one of them, or all of them? The intention was to apply to all, but some find it ambiguous.
  • Wondering whether we should have trailing commas or some other modified interaction with ASI.
  • Additional complexity and general confusion about the motivation.
  • There is some implementation burden (e.g., in Babel) for the comma-separated declarations, e.g., to ensure the decorator is evaluated once.

None of these are fatal; they would just be reasonable to spend more time thinking about before including in a Stage 3 proposal. My plan is to back these out for now, and work with @wycats to make it possible to propose this separately.

Context of "this" in properties definition

I've created a bug here babel/babel#6977 about ambiguous behavior of this in property definition, but looks like it could be not a bug.

Provide an example code here

class A  {
  state = { value: this.value }

  constructor(val) {
    this.value = val
  }
}

console.log('A.state.value', new A('A').state.value)


class BParent  {
  constructor(val) {
    this.value = val
  }
}

class B extends BParent {
  state = {value: this.value}
}

console.log('B.state.value', new B('B').state.value)

will produce

A.state.value undefined
B.state.value B

I can't find anything about context for this in https://tc39.github.io/proposal-class-fields/

Am I missing something? What's the expected behavior? For me using this in class properties must be restricted.

What about protected methods, better declaration of private fields, private async functions, and destructuring?

I opened an issue in tc39/proposal-private-methods#21, however I just realized that further discussion about the topic should be here. So, I will put again what I wrote there:

How could we allow a private field to be shared only when subclassing but not when it's an instance? Why not protected fields were not proposed? Have you thought if in the future somebody does propose them? (we are running out of characters)

I know it was discussed many times why # was chosen (I read the FAQs). It's ugly but I get it. However, that doesn't mean there cannot be another way of declaring them. What about this?:

class A {
  static prop0 = 123
  private prop1 = 123
  prop2 = 123 // public property
  method() { // public method
    console.assert(new.target.prop0 === 123) // static
    console.assert(this.#prop1 === 123) // private <- accessing changes
    console.assert(this.prop2 === 123) // public
  }
}
console.assert(A.prop0 === 123) // static (devs know it's not A.static.prop0)

Declaring them that way makes more sense besides it doesn't break the current proposal's idea.
However, if somebody comes up with "this may be confusing..." or similar, devs do not seem confused using static [prop], they know how to access a static var (new.target[propName]). In a similar way, private [prop] won't make it more confusing, devs will know to access with this.#[prop].

An example where it will look better:

class A {
  #$ = jQuery
  #_ = lodash
  method() { this.#$(...) }
}
class A {
  private $ = jQuery // You still have to access with this.#$
  private _ = lodash // You still have to access with this.#_
  method() { this.#$(...) }
}

What about async functions?

class A {
  #async method() {}
  // or
  async #method() {}
}

How will it behave with destructuring. For instance (with no Private fields):

class A {
  prop = 10
  constructor() {
    const { method } = this
    method()
  }
  method() { console.log('Not bound to "this" but works') }
}

With private fields:

class A {
  prop = 10
  constructor() {
    const { #method } = this // or { method } = this# ???
    #method() // ??? or not allowed, throws? (but what)
  }
  #method() { console.log('Not bound to "this" but works') }
}

Are private fields not enumerable?, I mean, is it possible to get all the names of private fields from inside a class? If they are not enumerable, how does it affect applying a function decorator and changing the descriptor to enumerable: true? got it :P

How does it act with Proxy?

class MyProxy extends Proxy {
  constructor() {
    return new Proxy(this, {
      get(target, propName) {
        if (typeof target[propName] === 'function')
          return target[propName]
        console.log(`Accessing [[${propName}]]`)
      }
    })
  }
}
class A extends MyProxy {
  one = 5
  prop = 10
  #prop = 20
  method() {
    this.prop
    this.#prop
    this.#method()
  }
  #method() {
    this.one
  }
}
new A().method()
// outputs: Accessing [[prop]]
// outputs: ??? Accessing [[#prop]]
// outputs: ??? Accessing [[one]] <- as a result of calling this.#method, does the trap still work?

So far I have more questions that actually use cases since I'm not pretty sure how much does it affect everything we know about JS. Now we have block scope vars and function scope vars. Maybe explaining how to categorize private block/function scope vars will clarify better all questions.

Remove shorthand private field access syntax

The shorthand syntax #x for this.#x has some recently-raised concerns:

  • Many people, seeing the private fields syntax, seem to think, "oh, this is great, I understand, it's a lexically scoped variable!" However, the semantics are different, referring to the this binding, which works differently from lexical scoping in methods and ordinary function literals. If methods or function literals inside methods refer to private fields, and pass this as a callback (without using bind), then it's likely to be invoked with the wrong receiver, and the "lexical scoping" illusion is broken.
  • Debugging wrong-receiver issues is hard for many programmers, and people have spent significant time learning how to deal with it. It might be harder for people to debug the new case than the previous case, as they won't see this syntactically in the code.
  • For static fields, it might make sense for the #x shorthand to refer to [[HomeObject]].#x rather than this.#x, since if you're referring to a static field from an instance method, you will otherwise be unable to use
  • We may want #x to refer to the private name itself, as was proposed earlier; if we use #x to refer to this.#x, we are precluding that possibility.
  • When refactoring from code using public fields to private fields, it's incongruous to remove the this..

Let's break this out into a follow-on proposal so we have more time to think through these questions more thoroughly (with @wycats as a probable champion). Although there was some concern raised about the mental model resulting from splitting out the proposal (that users may expect the lack of this. to explain why things are private, rather than understanding this from #), formally speaking, this feature is purely additive and separates out pretty straightforwardly.

How should initializers as functions interact with any stack trace/debugger API?

@bmeurer raised the question, if initializers are specified as methods, will this have runtime overhead? It would be nice if frontends could trivially inline initializers in the normal case. There are a few ways that this might be observable, though none of them are in the ECMAScript specification:

  • In function.caller: This one doesn't actually come up, since the entire class body is in strict mode. As a result, referring to the caller will throw (or sometimes return null in V8), even if it's calling out to sloppy mode code outside of the class body. It would never reveal the initializer method.
  • In the stack traces: I'm not sure whether or not initializers should show up separately in stack traces from the constructor (or wherever else super is called from). I'm guessing maybe they should, as they're not organized linearly, and having both pieces of information (where the constructor starts/super is called, and which line of code the initializer is on) are separately useful.
  • In introspective stack trace APIs: I have not looked across multiple engines, but it seems like in V8, getting the actual function identity from a stack trace is only available for sloppy mode code, and not strict mode code. This seems to mean that we would never leak the function for the initializer, even through the stack trace API.

Ultimately, it's a non-normative choice whether we use a method for initializers or fashion the scope in a more direct way. Methods are used in the current spec text to minimize the diversity of representations of scopes, to keep things analogous to other code, and to reduce the amount of spec machinery. But earlier drafts fashioned the scopes more directly. I'd be fine to switch back if it would be helpful.

cc @bmeurer @jaro-sevcik @bterlson @syg @kmiller68 @xanlpz @ajklein

Prototype properties :(

If I understand correctly, this proposal has succeeded the Orthogonal Classes proposal. Unlike that iteration, which allowed non-method, non-accessor properties of all three targets to be defined within a class body, this seems to omit prototype properties β€” i.e. post-declaration Object.defineProperty(Foo.prototype, ...) will still be required.

I found the reasoning behind this change at https://github.com/littledan/proposal-unified-class-features/blob/master/DETAILS.md#type-implies-placement

Not that it counts for much but ... I wish it weren’t so. I have used non-method value properties on prototypes often enough to think this is a weird hole; they're useful in particular for inheritance cases where only a subset of instances needs a dynamic value or a subset of extended classes have a different value.

ClassDefinitionEvaluation algorithm error?

2.11.2 ClassDefinitionEvaluation step 27 sets fields to the result of performing PropertyDefinitionEvaluation of each ClassElement m. If m is a field, the algorithm gets a list of records, each with a name, initializer, and static flag. However, if m is a method, it gets something else; tracing through the various abstractions I think it gets something like true, but I might have missed something along the way.

All of those are collected into the [[Fields]] slot and later used them assuming they are all records, apparently without filtering out the ones that weren't fields. What is going on here?

Should private fields be per class execution, or based on source position?

The current private fields specification would create multiple different field names #f in a case like this

function TagMixin(klass, name) {
  return class C extends klass {
    #f;
    static ["is"+name](instance) {
      try { instance.#f; return true; }
      catch (e) { return false }
    }
  };
}

class Foo extends TagMixin(Object, "Foo") { }
class Bar extends TagMixin(Object, "Bar") { }

Bar.isBar(new Foo());  // false

Feedback from several engineers in JSC (e.g., @saambarati) and V8 (e.g., @verwaest) has been that the implementation of the current design will could be significantly more complex to implement and possibly be slower while the JIT is warming up (read: longer startup time) than if each textual #f had just one private name for it. (Spec text in progress for what "textual" means there: tc39/ecma262#890 . Still some issues to work out.)

My question here: What do we think of the motivation for private fields being this amount of private, as they currently are in this proposal? The motivation, as I understand it, doesn't come from brand checking applications as I listed above (which you could also solve, with possibly worse or possibly better performance, using a WeakSet), but rather:

  • Private fields "desugar" into WeakMaps, where the natural unit of association would be one per class; we don't have other situations with a WeakMap created per piece of syntax. Further, aside from that template PR, there wasn't anything associated with a syntactic position at all, so it could be unclear how the specification should work.
  • The current design allows creating "defensible" class factories, where the entire point is that you can't see the private methods of other return values of the factory. Imagine a factory like this:
function SafeWrapper (fn) {
  return class {
    #callfn() { fn.call(this); }
    safeInvoke() { setupAction(); this.#callfn(); cleanupAction(); }
  };
}

Here, the purpose of the class is to take the given function and wrap it in something which will only let it be called in certain ways. If we let different return values of SafeWrapper be "friends" with each other just for using the same syntax, they'll be able to call each others' unguarded underlying function, which would be inappropriate.

Where I'm stuck is, I can't think of any practical use cases where this particular notion of defensibility is important. Does anyone have an example here where it's come up, either in JS or other programming languages?
cc @allenwb @erights @dtribble

Document `this` bindings in the ReadMe ?

It's a bit hard to tell from the ReadMe what this will be bound to in a static or instance initializer. For instances it seems to be the object being created. For a static field, would it be the class prototype?

Use of this pointer with private fields

In all of the listed examples, a private field of a class is accessed / used within methods or the constructor by using the this keyword, which I'm not sure if that's supposed to be intended or not, but also has some nasty consequences. For example:

   class Controller { 
        #id = '';

         constructor() {
              /* here using 'this' is safe always b/c we know new was invoked */

            let el = document.createElement('div');
                 
            el.id = 'controller_' + Math.round(Math.random() * 10000);
            el.addEventListener('click', this.onClick); /*purposefully not bound for example / saving memory */
            this.#id = el.id + '_jsobj_' + (Math.round(Math.random() * 10000);
            document.body.appendChild(el);
       }

      onClick(evt) {
           //this is either undefined or the given element, so this.#id won't work
     }
}

Another example would be where a 'class' inherits from another class but with out using JS 'class' syntax and thereby not calling the super constructor

   class Point2D {
         #x  = 0;
         #y = 0;
         #str = '';

         constructor(x, y) {
              this.#x = (typeof x === 'number' && !isNaN(x)) ? x : (Number(x) || 0);
              this.#y = (typeof y === 'number' && !isNaN(y)) ? y : (Number(y) || 0);

              this.#str = this.#x + ', ' + this.#y;
         }

         toString() { return this.#str; }

}
     
function Point3D(x, y, z)
{
     this.x  = x;
     this.y = y;
     this.z = z;
}

Point3D.prototype = Object.create(Point2D.prototype);


var oPoint3D = new Point3D(x, y, z);

oPoint3D.toString(); //?What gets output

The first example is bad news imho, b/c there is no way to know the true value of this unless the dev writing the function always does a check up front every time to ensure a valid this pointer, but then even in that case we get to the 2nd example.

The 2nd example is a bit non-sensical, but it is still important b/c the oPoint3D will be an instanceof oPoint2D eventhough a super was not called. I assume that example would just return the empty string value? But the point is even if you were to validate the this pointer every single time, you still may not be able to do so properly b/c you have no way to truly ensure construction occured (at least not easily).

I can think of other examples as well (such as what if a class method itself is a constructor). . .what are the thoughts around this?

Early Errors fix: ClassElementName instead of PropertyName

In the first listed Early Error I believe there is a small mistake.

The original spec:

FieldDefinition :
    PropertyName Initializer

I believe this should be:

FieldDefinition :
    ClassElementName Initializer

As the arguments and super() ought to be forbidden from use in both private and public class field initializations?

Proposal: make declarative keywords, and/or qualifiers mandatory

After some thinking and the discussion here I want to propose making the let, var, const, static qualifiers mandatory for declarations.

It seems to me it would be good (visual) practice and would rather adhere to most of the OO languages. Also this might mitigate lots of programming bugs. It's especially useful since the we generally discourage declarations like x = 5.

I hear that there are more arguments to that, that have been partially discussed in this meetings notes and I am interested in gathering them.

Public fields should be configurable

Not sure how this slipped through the cracks, but public fields seem to be defined as non-configurable. However, previous discussion concluded that they should be configurable (for analogy with methods, which are also configurable). Fix DefineField to define them as configurable.

Reflection from superclasses

Would it be possible for this proposal to include support for reflection over instance properties from a constructor in a superclass? This would be very useful to support something like named constructor parameters, as described in #33. Here's the example again (somewhat modified for clarity for this new thread):

class Entity {
  constructor(attributes) {
  
    //how can we obtain a list of instance property names defined on the child class?
    const propertyKeys = ???
    
    for (let key in propertyKeys) {
      if (key in this) {
        this[key] = attributes[key]
      }
    }
  }
}

class Cat extends Entity {
  name = null
}

const garfield = new Cat({
  name: 'Garfield',
  someExtraProperty: 'foo'  // this will be ignored
})

garfield.name               // 'Garfield'
garfield.someExtraProperty  // undefined

My current understanding of the class fields proposal is that the definition of Cat here would be equivalent to:

class Cat extends Entity {
  constructor(attributes) {
    super(attributes)
    this.name = null
  }
}

Assuming that's the case, then assigning property values in the Entity constructor wouldn't work, since the property isn't defined until after the call to super(). My initial thought was that putting the properties on the prototype in addition to the instance would solve this, but I understand now that this causes too many other issues. But I think there should still be a way to accomplish something like the above, as can be done in other languages like PHP and Java (and probably others).

For this particular example, decorators might provide an alternative solution, since decorators are evaluated early on, e.g.:

class Cat extends Entity {
  @attribute
  name
  
  @attribute
  curious = true
  
  ...
}

(Note: I'm making several assumptions here about the interaction between classes and decorators that I don't think are finalized yet since decorators are still in stage 2.)

Still, I imagine there are other situations where it would be useful to use Object.getOwnPropertyNames or Reflect.ownKeys from a superclass and have it include the names of instance properties defined in a child class.

But Object.getOwnPropertyNames should be semantically correct, and not return properties that don't exist yet...so I suppose what I'm really proposing is that class fields should be evaluated like this:

class Cat extends Entity {
  constructor(attributes) {
    this.name = null
    super(attributes)
  }
}

...which of course wouldn't be valid JS if actually written that way (since you can't reference this before calling super()), but would it be feasible for class fields to work this way behind the scenes? If not, maybe there could be a new reflection method that would mean something like, "tell me the names of properties declared in my class or any of my subclasses, regardless of whether or not they've been initialized yet".

Sorry for the somewhat meandering description; I was trying to cover various considerations and I hope it wasn't confusing.

this.onclick = this.clicked.bind(this);

This may be a wrong place/proposal to ask this, so I apologize in advance. I searched, but couldn't find a similar question. My search-foo may not be strong enough.

The following keeps raising eyebrows in every discussion I've come across (I've seen this especially in React-related discussions because you immediately run into it in the handling events section):

  constructor() {
    super();
    this.onclick = this.clicked.bind(this);   // <-- this raises eyebrows
    this.x = 0;
  }

The universal answer to this.onclick = this.clicked.bind(this); is to (ab)use class fields (the aforementioned react docs do the same):

class Counter {
    clicked = () => <do something>

    constructor() {
        super();
        this.x = 0;
    }

   // ... use this.clicked with no extra .bind(this) ...
}

So, the question is:

  • is this something that will stay this way forever?

  • is there a proposal to change the behaviour of class methods?

Private constructor

Should users be allowed to declare the private field #constructor?

Reserving this field for future use might be appealing.

Private Properties Syntax

I have opinion against syntax for private properties and methods in this proposal.

Many languages have keywords private, protected, public. I suggest to add that keywords against # syntax. It's looks like you guys reinventing the wheel, but we have that syntax many years in languages like Java or C++ or PHP etc.
And making JavaScript with "something" new is harm to JavaScript. I think Javascript need to be more closer to other languages like Java or C# or TypeScript (where we have private/protected/public modificators). And don't create your "own" things.

Default placement of instance properties (prototype vs. instance)

It looks like in the current proposal, public properties declared without the static modifier are added to the instance by default, i.e. this:

class Demo {
    x = 0
}

Translates to this:

class Demo {
    this.x = 0
}

I'm a little confused when looking at the spec though, specifically this part of DefineField() (last step):

Perform ? DefinePropertyOrThrow (F, fieldName, desc).

I may be reading it incorrectly, but I thought that F was a reference to the constructor function / class - makes me wonder if it was a typo and it was supposed to beO there instead of F, but I'm not well-versed in reading these specs so maybe not.

In any case, assuming my understanding is correct that properties will go on the instance by default, I wanted to bring up two previous threads on this subject:
tc39/proposal-class-public-fields#38
tc39/proposal-class-public-fields#59

People have brought up a number of good reasons why it would be good for properties to go on the prototype by default. Here's an example use case where the difference matters:

class Entity {
  constructor(attributes) {
    this.assignAttributes(attributes)
  }
  
  assignAttributes(attributes) {
    for (let key in attributes) {
      if (key in this) {
        this[key] = attributes[key]
      }
    }
  }
}

class Cat extends Entity {
  name = null
}

const garfield = new Cat({name: 'Garfield'})

garfield.name  // null

If the fields are only defined on the instance, then the parent class has no way of knowing about them in the constructor.

I saw @littledan's comment in #3 that the current proposal leaves open the possibility of modifiers (e.g. own and shared) to explicitly specify where properties should be defined. I think that's a great idea, but I still think the prototype is a better default than the instance, at least for primitives. It could of course be problematic for object and array properties - those shouldn't be shared by default among instances, and if they were it would be surprising and a source of bugs. But it would be simple enough to set all non-primitives to null on the prototype and set their real initial values in the constructor (perhaps only if the parent constructor(s) didn't already initialize them - not sure about that part). Actually, even primitives could be set to null on the prototype if there is some reason that it would be better not to set their initial values there (although I can't think of one). The main concern I wanted to bring up is that the properties declared inside the class should somehow be visible on the prototype -- even if only the property names and not their real default values.

There may be other, better solutions to the use case above. I suppose what I'm really advocating is some kind of ability to do reflection on instance properties from parent classes or outside the class scope entirely (without needing to actually create an instance to do it). So I'd be interested to hear any alternative suggestions, or further explanations as to why the current proposal is as it is.

DefineFields: how to tests observable abrupt completion for field with IsAnonymouseFunctionDefinition=true

Hello,

In DefineFields, we check if HasOwnProperty(initValue, "name"):

7. If fieldRecord.[[IsAnonymousFunctionDefinition]] is true, then
    a. Let hasNameProperty be ? HasOwnProperty(initValue, "name").
    b. If hasNameProperty is false, perform SetFunctionName(initValue, fieldName).

In ClassFieldDefinitionEvaluation, IsAnonymouseFunction definition is set in step 3.e:

Let isAnonymousFunctionDefinition be IsAnonymousFunctionDefinition(Initializer). 

Is it possible for hasNameProperty in DefineFields 7.a to ever be true? Can an anonymous function get a name value between the field record creation and the field initialization?

Additionally, I'm curious to set up a scenario to observe the abrupt completion in 7.a (? HasOwnProperty...) and not sure how to set that up. But if the step 7.a turns out to be unnecessary I won't test it!

Thanks!

Suggestion: let shorthand syntax refer to static properties instead

I learnt that the only way to refer to static properties is by Foo.x or this.constructor.x, for example in

class Foo {
  static x; //number of Foo objects
  constructor() {
    // make Foo
    x++; // suggestion: this is a reference to the static x, instead of writing Foo.x
  }
}

This would work the same for private fields (and existing shorthand will have to be dropped in favour of this). The inspiration behind this idea is that this is how Java handles static properties being referenced from within a class.

This would improve usability of static properties considerably.

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.