All 3 options, with regards to this, behave the same.
Option 3 is creating a new function on every render and is thus less desired than options 1 and 2 (since such re-creation is unnecessary and could potentially hinder performance)
As far as options 1 and 2, they come down to the same behavior with slight differences unrelated to the behavior of this.
The trick to understanding why they behave the same with this is to know what the following code does:
method = () => {}
It's just syntactic sugar to add a method to an instance:
class A {
method = () => {}
}
// is the same as
class A {
constructor() {
this.method = () => {}
}
}
See how Babel transpiles it.
Since an arrow function inherits the context it is defined in as this, the context for option 2 is the class itself.
class A {
constructor() {
this.method = () => {
return this;
// ^^^^ this points to the instance of A
}
}
}
const a = new A();
console.log(a.method() === a); // true
Which is the same thing for option 1:
class A {
constructor() {
this.method = this.method.bind(this);
}
method() {
return this;
// ^^^^ this points to the instance of A
}
}
const a = new A();
console.log(a.method() === a); // true
That means your options come down to the difference between:
// option 1
constructor(props) {
this.onChange = this.onChange.bind(this)
}
onChange() {
// code for onChange...
}
and
// option 2
constructor(props) {
this.onChange = () => /* code for onChange */
}
The main advantage I would give to option 1 is that it has a named function instead of an arrow function, which makes debugging a bit easier when examining stack traces (although function name inferences dilutes this point a bit).
The main advantage I would give to option 2 is that it's a bit "cleaner" looking, as in less verbose, code but that is a subjective opinion.
Overall, option 1 and option 2 are practically indifferent.