0

There are already several questions on this topic, and I've looked at all I've found - my question is in order to clear up for myself some (apparent) contradictions that I'm seeing. I suspect there is a better solution than the only one I have working right now. I'm pretty new to Javascript.

I've read through the scoping rules on this as described by e.g. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this . My understanding from reading is that although a lot depends on calling context, if a function is a method of an object then within the function this will be the object itself. I thought this rule trumped the other rules, but perhaps I have misunderstood.

I've also read a post at https://medium.com/byte-sized-react/what-is-this-in-react-25c62c31480 which essentially says that if I want to access object state in a method via this, and I have code like

class App extends Component {
  constructor(props) {
    super(props);
  }
  clickFunction() {
    console.log(this.props.value);
  }
  render() {
    return(
      <div onClick={this.clickFunction}>Click Me!</div>
    );
  }
}

then I need to explicitly bind the object to clickFunction() by either adding a line to the constructor like

this.clickFunction = this.clickFunction.bind(this);

or by using arrow notation to define clickFunction(), like

const clickFunction = () => { ... }

The first of these solutions works for me - I have not got the second working. It seems strange to me that I need to use either solution since (a) it seems to contradict what I thought was the assertion in the docs that an object method will treat the object as this, and (b) I don't see people doing this sort of thing in other tutorial examples that I look at.

For instance, there is a React tutorial at https://reactjs.org/tutorial/tutorial.html which defines object methods like renderSquare(i) and does not at any point explicitly bind these methods to the object.

If I try to do something which seems to me completely analogous to that tutorial, and don't explicitly add lines to the constructor to bind each method to the object, i.e. lines like this.clickFunction = this.clickFunction.bind(this), then I can't get my code to work.

Can anyone explain to me what I am misunderstanding about the tutorial and the documentation - why does the tutorial work even though there is no explicit binding to the object? The only difference I can spot between it and my own code is that I have use strict. Is there a better fix than the this.clickFunction.bind(this) solution I'm currently using? Adding one extra line of code to the constructor per method, to explicitly bind all my methods, seems pretty clunky.

user2428107
  • 3,003
  • 3
  • 17
  • 19
  • 1
    you can bind it here `
    Click Me!
    `
    – Deckerz May 02 '19 at 12:03
  • 1
    replace this `const clickFunction = () => { ... }` with `clickFunction = () => { ... }` – Harish May 02 '19 at 12:05
  • Thanks Harish and Deckerz, both those solutions work. The arrow notation is definitely nicer than my current fix so I guess that answers the most important part of my question - if you post it as an answer I'll accept it. I guess I misunderstood which scoping rules take precedence. I still don't understand why the tutorial I linked to doesn't seem to do any of these things. – user2428107 May 02 '19 at 12:10
  • 1
    In the docs, they don't need to bind the `renderSquare` since it is called within the `render` method. All lifecycle methods are already bound automatically, so `renderSquare` works, it doesn't need to bind again because it is called from somewhere already bound to `this`. Where do you need to bind `this`? Wherever something creates its own scope, ie `onClick` handlers. – devserkan May 02 '19 at 12:12
  • 1
    Also, please [see](https://stackoverflow.com/questions/52031147/react-which-is-recommended-arrow-or-normal-function/52031676). – devserkan May 02 '19 at 12:13
  • Actually devserkan, I am using a function which I'm also calling from `render()` and I couldn't get it working. I had a function `showTask(i)` which, coincidentially, was I think exactly analogous to `renderSquare(i)` - i.e. it's being called by `render()` in a loop or map in order to render a bunch of similar sub-components. I couldn't get my `showTask(i)` function to work without binding / arrow notation. Hence my confusion! So far as I can tell I'm doing something extremely similar to the tutorial yet need the binding. I can post my code if you're interested to help diagnose :) – user2428107 May 02 '19 at 12:24
  • Possibly it's that I have `use strict`, so even though `showTask(i)` is being called from somewhere already bound to `this` (i.e. from `render`), it ends up with the wrong scoping? – user2428107 May 02 '19 at 12:25
  • 1
    If you use a regular function in your `map` method then it creates its lexical scope again, this is why you should use an arrow function there or bind it again. Don't forget, if something creates its own scope you always consider binding `this` in that function or use another solution such as an arrow function. [See](https://stackoverflow.com/questions/31866390/how-do-i-get-the-right-this-in-an-array-map) for examples. – devserkan May 02 '19 at 12:47
  • Ah, so the culprit could be `map`? That would explain it! Thanks :) – user2428107 May 02 '19 at 13:59

5 Answers5

4

Arrow functions binds your functions to your class directly. But when if you use

const clickFunction = () => { ... }

This will create a inner function, and does not bind it to the class.

You can use

clickFunction = () => { ... }

which will do similar to

this.clickFunction = this.clickFunction.bind(this);

Harish
  • 1,841
  • 1
  • 13
  • 26
  • Thanks! What is "an inner function"? Actually sorry, I do know what that is, but I don't understand why const makes it more "inner" than no const. What is it inner to? – user2428107 May 02 '19 at 12:14
  • Does it create an inner function? It is not happening in class fields I guess. You can't create a function there like this. – devserkan May 02 '19 at 12:16
  • @user2428107 can you go through https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#Nested_functions_and_closures – Harish May 03 '19 at 04:10
1

You're right that, when calling a function from an object, the this keyword ends up being that object. So running something like:

const app = new App();
app.clickFunction();

Would yield the result you're expecting, because it's being called directly from the App class.

However, when you use that function as an event handler in your JSX, you're passing a reference to that function as a callback. By default, the function's this keyword is not determined until you call it, so it will be assigned based on the lexical context it's called from. You could imagine, when you set the handler, something like the following is happening under the hood:

const callback = app.clickFunction;
// click event happens
callback(event);

Here you can see that the call to callback() is just a bare function call. There's no app. prefixing it to provide the lexical context for this.

The two ways around this behavior, which you've already listed, explicitly set the this keyword to the object they originally live on. Calling this.clickFunction = this.clickFunction.bind(this) is the most explicit in that it takes a normal function and manually binds this to the value of this during object construction. Which will be the object being constructed.

Arrow functions do the same thing, but without the explicit binding. This is actually a functional difference between arrow functions and regular functions that a lot of people gloss over since they're typically chosen for stylistic or brevity purposes. Arguably, arrow functions behave as most programmers would expect, while normal functions behave in a way that's pretty unique to Javascript (and therefore pretty confusing).

Erik Tate
  • 131
  • 4
  • Thank you! Very clear. That explains at least some cases where I thought I understood the scoping rules but they weren't working the way I expected. – user2428107 May 02 '19 at 12:39
0

Second arrow method, it will take this reference where the method is defined not from where it is called. so it will take this reference of class.

Ref. this in arrow function

Jitesh Manglani
  • 495
  • 5
  • 12
0

You have valid points

Why tutorial worked? If you see renderSquare implementation you will notice that it is not using this in its implementation so it does not have to bind itself with this. Your implementation might not working because you may be using this inside method implementation.

renderSquare(i) {
  return <Square value={i} />;
}

When you get references like this.clickFunction, you are only getting reference of that particular function which is not bind to any object, which is why calling it will fail if you try to refer variables using this

See this fiddle https://jsfiddle.net/yh3jw5nk/1/ for more explaination

  • In the tutorial, `renderSquare(i)` starts off not referring to `this`, but later in the tutorial it's updated to deal with state and instead looks like `renderSquare(i) { return ; }` – user2428107 May 02 '19 at 12:36
  • You are right but renderSquare(i) is still associated with this class. Suppose we assign this.renderSquare to click function then resulting function assigned to variable would loose its association... make sense? – Saurabh Chouhan May 02 '19 at 12:47
  • Right, that makes sense. But the reason I'm puzzled is that I'm calling my equivalent of renderSquare() from render() as well, so mine should also still be associated with the class. It's probably impossible to diagnose why my version isn't working (without binding) unless I post my code, but so far as I can see it is analogous. Edit: another user has suggested it could be that I'm using `map()` and that this creates a new lexical scope. – user2428107 May 02 '19 at 13:59
0

This is determined at runtime and depending on the code, it can be something different.

this is

  • determined at runtime, when a function is envoked
  • determined by how a function is invoked, not where the function is defined
  • a reference to an object.
  • will always be an object
  • global (this) not available in strict mode

Example 1: this = window

var name = 'Global';

var callName1 = function() {
  var name = 'Peter';
  console.log('--- From callName1 ----');
  console.log(this.name);
  //console.log(this);
  callName2();
}


var callName2 = function() {
  var name = 'Jane';
  console.log('--- From callName2 ----');
  console.log(this.name);
  //console.log(this);
}

callName1();

var execute = function(fn) {
  var name = 'Mary';
  console.log('--- From execute ----');
  console.log(this.name);
  //console.log(this);
}

execute(callName2);

Example 2: not available in strict mode

'use strict';

var name = 'Global';

var callName1 = function() {
  var name = 'Peter';
  console.log('--- From callName1 ----');
  console.log(this.name);
  console.log(this);
}

callName1();

Example 3: examining this with method invocation

var name = 'global';

var obj = {
  name: 'James Obj1',
  func: function() {
    console.log('--- From func ----');
    console.log(this.name);
    console.log(this); // this reference obj1
  }
}

obj.func()

var obj2 = {
  name: 'Jame Obj2',
  func: obj.func // this reference obj2, but the function is defined in obj1
}

obj2.func()

var obj3 = {
  name: 'Kane Obj3',
  obj4: {
    name: 'Mary Obj4',
    func: function () {
      console.log('--- From obj4 ----');
      console.log(this.name);
      console.log(this); // this reference obj4
    }
  }
}
obj3.obj4.func()

With () => {} function this - is lexically bound. It means that it uses this from the code that contains the arrow function.

Junius L
  • 15,881
  • 6
  • 52
  • 96