Binding context when calling ES6 method. How to access object from within method called as callback?
Solution 1:
React.createClass (ES5) way of creating class has a built-in feature that bound all methods to this
automatically. But while introducing classes
in ES6 and migrating React.createClass, They found it can be a little confusing for JavaScript developers that are not used to this feature in other classes, or it can be confusing when they move from React to other classes.
So, they decided not to have this built-in into React's class model. You can still explicitly prebind methods in your constructor if you want like
class WeatherProject extends Component {
constructor(props) {
super(props);
this.state = {
zip: ''
};
this._handleTextChange = this._handleTextChange.bind(this); //Binding to `this`
}
_handleTextChange(event) {
console.log(event.nativeEvent.text);
this.setState({zip: event.nativeEvent.text})
}
But we always have a simple way to avoid this prebinding. Yeah! You got it. Arrow functions.
class WeatherProject extends Component {
constructor(props) {
super(props);
this.state = {
zip: ''
};
}
_handleTextChange = event => {
console.log(event.nativeEvent.text);
this.setState({zip: event.nativeEvent.text})
}
render() {
return (
<TextInput
style={styles.input}
onSubmitEditing={this._handleTextChange}/>
);
}
}
BTW, This is all regarding React. ES6 class always has a way of accessing the context of an object from within a method without explicitly binding the object back onto it's own method.
class bindTesting {
constructor() {
this.demo = 'Check 1';
}
someMethod() {
console.log(this.demo);
}
callMe() {
this.someMethod();
}
}
let x = new bindTesting();
x.callMe(); //Prints 'Check 1';
But this doesn't prints 'Check 1' if we call it in JSX Expression.
EDIT :: As @Oka mentioned, arrow functions in class bodies is ES7+ feature and available in Compiler/polyfills like babel. If you're not using transpiler that support this feature, We can just bind to this
as mentioned above or write a new BaseComponent like this ( Which is a bad idea )
class BaseComponent extends React.Component {
_bind(...methods) {
methods.forEach( (method) => this[method] = this[method].bind(this) );
}
}
class ExampleComponent extends BaseComponent {
constructor() {
super();
this._bind('_handleTextChange', '_handleClick');
}
// ...
}
Solution 2:
Breaking away from ES6, and React for a moment, in regular old JavaScript, when you pass an object's method around it is just a reference to the function itself, not the object and the function.
Any invoking function can choose to use the implicit this
, by calling the function normally, or it can even choose to change the context using .call
and .apply
, or .bind
.
var O = {
foo: function () {
console.log(this);
},
bar: 51
};
O.foo(); // `this` is O, the implicit context, inferred from the object host
var foo = O.foo;
foo(); // `this` is is the global object, or undefined in Strict Mode
What is happening is, you are passing the _handleTextChange
function to an event emitter, which gets executed some time later. The event emitter has no idea that it received the function as a method. It just executes it.
var O = {
foo: function (event) {
console.log(this);
console.log(event);
},
bar: 51
};
function invoke (func) { // the function reference held by O.foo is now held by `func`
func('some param'); // it has no idea as to what the contextual `this` is bound to, maybe its implicitly global, maybe its explicitly bound, maybe its undefined
}
invoke(O.foo);
invoke(O.foo.bind(O)); // A copy of the function with the context explicitly bound
Take a look at context sharing:
function foo () {
console.log(this.a);
}
// Both object hold references to the foo function
var O = { foo : foo, a : 5 },
O2 = { bar : foo, a : 1 };
O.foo(); // implicit `this` from O
O2.bar(); // implicit `this` from O2
var Z = { a : 23 };
O.foo.call(Z); // explicit `this` from Z
You can get arbitrarily complicated with these examples.
Solution 3:
Perhaps I've misunderstood a fundamental aspect of JavaScript (I'm a beginner in JS), but this seems completely insane to me. Is there really no way of accessing the context of an object from within a method without explicitly binding the object back onto... it's own method, at the point where it is called?
Well, "methods" are not owned by objects in javascript. All functions are first-class values (objects), and not "part" of an object or a class.
The binding of the this
value happens when the function is called, depending on how the function is called. A function call with method notation has its this
set to the receiving object, an event listener call has its this
set to the current event target, and a normal function call only has undefined
.
If you are accessing a method on an instance, it's really just the standard prototype inheritance property access which yields a function, which is then called and dynamically-bound to the receiver.
If you want to call a function as a method on an instance when it is an event listener, then you need to explicitly create a function that does this - the "bound" method. In ES6, the arrow notation is usually preferred for this.