Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Does this include auto-binding of class property functions? #139

Closed
mjhm opened this issue Oct 8, 2018 · 9 comments
Closed

Does this include auto-binding of class property functions? #139

mjhm opened this issue Oct 8, 2018 · 9 comments

Comments

@mjhm
Copy link

mjhm commented Oct 8, 2018

I can't tell from this summary if this includes the auto binding feature from https://babeljs.io/docs/en/babel-plugin-proposal-class-properties

That's a pretty important feature that removes a bunch of boilerplate from constructor functions. It seems to me that it would be worthwhile describing, if it is indeed included (and possibly worth noting otherwise if it's not).

I'd be happy to PR a documentation update.

@bakkot
Copy link
Contributor

bakkot commented Oct 8, 2018

Yes, in the following sense: class property initializers are run every time the class is instantiated and this in the context of initializers refers to the instance being constructed, which has the effect that an arrow function in a class field initializer which refers to this will always refer to the instance being constructed - i.e., this works

class A {
  field = () => this;
}
let a = new A();
(0, a).field() === a; // true

for the same reason this does:

class A {
  field = this;
}
let a = new A();
(0, a).field === a; // true

@mjhm
Copy link
Author

mjhm commented Oct 8, 2018

So if I'm not mistaken the example can be simplified to:

class Counter extends HTMLElement {
  x = 0;

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

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

  connectedCallback() { this.render(); }

}
window.customElements.define('num-counter', Counter);

@jkrems
Copy link

jkrems commented Oct 8, 2018

IIRC one issue with using arrow functions for "bound methods" is that you're creating new functions for each instance of the class instead of having the same method being bound to each instance. Using this pattern is not the same as "auto-binding".

@mjhm
Copy link
Author

mjhm commented Oct 8, 2018

Interesting -- though doesn't this.onclick = this.clicked.bind(this); in the constructor create a new function per instance as well (unless there's compiler magic happening under the hood). Though perhaps in my example the render = () => ... is overly aggressive keystroke reduction.

@jkrems
Copy link

jkrems commented Oct 8, 2018

It does create a new object but not a new function. E.g. the same code gets executed so an engine gets the opportunity to actually optimize it (as opposed to getting "fresh" code each time).

@mjhm
Copy link
Author

mjhm commented Oct 8, 2018

I get what you're saying but I'm seeing something empirically opposite. It looks like (at least in Node8) that defining and assigning arrow functions are much more efficient than bind.

test_arrow.js

var i = 100000000
var a = null
while (i--) {
  a = (b) => b;
}
/usr/bin/time -l node test_arrow.js
        0.16 real         0.13 user         0.01 sys
  23072768  maximum resident set size

test_bind.js

var i = 100000000
var tstfn = function (b) { return b }
var a = null
while (i--) {
  a = tstfn.bind(this)
}
/usr/bin/time -l node test_bind.js
        1.53 real         1.32 user         0.10 sys
  26087424  maximum resident set size

@mbrowne
Copy link

mbrowne commented Oct 8, 2018

It looks like your original question has been answered - yes, this proposal will allow you to create bound functions by using arrow functions in class properties. Although this use of arrow functions has become a common practice in the React community, it's probably not the best idea and incidentally can create problems with certain testing tools such as Enzyme. For a detailed explanation of the shortcomings of arrow functions in class properties, see https://github.com/tc39/proposal-decorators/blob/master/bound-decorator-rationale.md, which also shows a better solution that will be possible with the introduction of decorators. (Actually there are already some older open-source implementations of the same concept using the legacy decorators spec.)

For an even deeper dive into this issue, check out #80. Regarding performance, see https://medium.com/@mharrisonb/also-youre-right-641899d140cb. At least in Chrome, bind() is faster in this particular situation. (Note: jsperf.com seems to be down at the moment, but in summary make sure you're doing a realistic performance test in which the arrow function is actually created as a class property; in other situations such as your above test, arrow functions may indeed be faster than bind().)

@mbrowne
Copy link

mbrowne commented Oct 8, 2018

P.S. If after considering all of the above, for whatever reason you still choose to use arrow functions in class properties for binding, I recommend at least making the property private. That way if your class is ever extended (however unlikely), it's less likely to cause inheritance-related issues.

@littledan
Copy link
Member

Thanks for the excellent question and answers everyone. I think we can say "case closed" to this mystery.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants