tc39 / proposal-class-fields

Orthogonally-informed combination of public and private fields proposals
https://arai-a.github.io/ecma262-compare/?pr=1668
1.72k stars 113 forks source link

Clarify what the context (`this`) for fields declared as anonymous functions will be in the spec. #312

Closed theengineear closed 4 years ago

theengineear commented 4 years ago

If you declare a field and set it to an anonymous function expression, what will be the this when that function is invoked? E.g., from the README.md file, would this work?

// Current
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);
// With field declarations
class Counter extends HTMLElement {
  x = 0;

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

  render = () => {
    this.textContent = this.x.toString();
  }

  connectedCallback() { this.render(); }
}
window.customElements.define('num-counter', Counter);

The motivation surrounds the stability of the context and whether or not they need additional bind-ing. Often, when authoring a custom element, I wish I had an idiomatic way to declare that an instance method should always have the this context set to the host element. It feels unnecessarily verbose to do this in the constructor block.

It occurred to me when reading this spec that we might now have a natural way to ensure the this argument is always what we want it to be.

Note that either way (via bind or via anonymous function declaration) we're creating new methods per-instance (instead of reusing a prototype method). I.e., you wouldn't want to declare all your instance methods like this, but it seems useful for certain cases.

bakkot commented 4 years ago

this in the context of class field initializers is indeed the instance being invoked, just as it would typically be for a method. This has the consequence that if you initialize the field to an arrow expression which refers to this, that will always refer to the instance on which that field is installed.

Since this is shipping in some browsers now, you can just try it:

class A {
  x = () => this;
}
let o = new A();
(0, o.x)() === o // true
theengineear commented 4 years ago

Thanks for the fast response @bakkot! I'll close this ticket out. As an aside, you might choose to highlight this fact in the main readme since I think developers might find it intriguing — especially in the context of custom elements.