133

I'm new to JavaScript. New as far as all I've really done with it is tweaked existing code and wrote small bits of jQuery.

Now I'm attempting to write a "class" with attributes and methods, but I'm having trouble with the methods. My code:

function Request(destination, stay_open) {
    this.state = "ready";
    this.xhr = null;
    this.destination = destination;
    this.stay_open = stay_open;

    this.open = function(data) {
        this.xhr = $.ajax({
            url: destination,
            success: this.handle_response,
            error: this.handle_failure,
            timeout: 100000000,
            data: data,
            dataType: 'json',
        });
    };

    /* snip... */

}

Request.prototype.start = function() {
    if( this.stay_open == true ) {
        this.open({msg: 'listen'});
    } else {

    }
};
//all console.log's omitted

The problem is, in Request.prototype.start, this is undefined and thus the if statement evaluates to false. What am I doing wrong here?

Carson Myers
  • 37,678
  • 39
  • 126
  • 176
  • Is there a reason you have `start` in the `prototype`? – xj9 Oct 25 '10 at 03:56
  • What's `Request.prototype` set to? – Matt Ball Oct 25 '10 at 03:57
  • I had a similar question here: http://stackoverflow.com/questions/3198264/javascript-how-do-i-retain-a-reference-to-a-request-initiator-in-a-handler in which there are a bunch of helpful links. The crux of it is that `this` in JavaScript is _not_ a constant reference to the 'owner' of a prototypal function being called, like it would be in most OO languages like Java. – Marc Bollinger Oct 25 '10 at 04:01
  • 1
    @Matt: Request is a constructor function. Request.prototype defaults to `new Object()`. Anything you add to it automatically becomes properties of objects created using `new Request()`. – Chetan S Oct 25 '10 at 04:02
  • @Matt Ball `Request.prototype` is where instances of `Request` inherit from. In this case it is probably `Function` or `Object`. – xj9 Oct 25 '10 at 04:04
  • @Chetan and @indie: I know how prototypes work in JavaScript, but I'm asking about _this_ object's prototype. Perhaps the OP has not shown all the code. – Matt Ball Oct 25 '10 at 04:10
  • Sorry, yes, this is all the code – Carson Myers Oct 25 '10 at 17:39
  • Possible duplicate of [How to access the correct \`this\` inside a callback?](https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback) – Michael Freidgeim May 08 '19 at 09:10
  • 1
    I mean, that question was asked 3 years later than this one – Carson Myers May 08 '19 at 09:58

9 Answers9

170

I just wanted to point out that sometimes this error happens because a function has been used as a high order function (passed as an argument) and then the scope of this got lost. In such cases, I would recommend passing such function bound to this. E.g.

this.myFunction.bind(this);
EliuX
  • 11,389
  • 6
  • 45
  • 40
  • 5
    Good explanation !! That was exactly what I was looking for. – vandersondf Dec 19 '19 at 17:12
  • 3
    there is no telling how much headache you just saved me – Native Coder Oct 23 '20 at 03:41
  • 5
    Why does the scope of `this` get lost? – theonlygusti Nov 12 '20 at 14:14
  • 4
    Good question! I hope this helps: https://eliux.github.io/javascript/common-errors/why-this-gets-undefined-inside-methods-in-javascript/ – EliuX Jan 28 '21 at 03:32
  • 2
    Thanks. This is the correct anwer! Why isn't this accepted? – user1511417 Mar 15 '21 at 12:04
  • This happens a lot in Modules, which have no 'this' by default... you have to make a `this`, typically an instance of somebody's daughter – Ray Foss May 06 '21 at 23:03
  • 1
    Or you can use an arrow function then you won't have to call the bind. – Waqar Rashid Jun 28 '21 at 11:53
  • 1
    I've seen this `bin` function often as a fix for an unexpected behavior of `js`, but _not once_ as a useful feature in any project whatsoever. 100% it's used to bind what you thought it was bound to in the first place. – Nearoo Aug 25 '21 at 21:40
  • Is there a way I can implement my class (using the `class` keyword) without loosing this when the object's function gets passed around? – DarkTrick Nov 08 '22 at 09:45
  • 1
    @DarkTrick The solution is not in the implementation of the `class`, but how we invoke a function. So, keep creating your class as usual, but when you pass their functions/methods as high order functions use `bind` or an arrow function(lambda). – EliuX Nov 08 '22 at 14:45
86

How are you calling the start function?

This should work (new is the key)

var o = new Request(destination, stay_open);
o.start();

If you directly call it like Request.prototype.start(), this will refer to the global context (window in browsers).

Also, if this is undefined, it results in an error. The if expression does not evaluate to false.

Update: this object is not set based on declaration, but by invocation. What it means is that if you assign the function property to a variable like x = o.start and call x(), this inside start no longer refers to o. This is what happens when you do setTimeout. To make it work, do this instead:

 var o = new Request(...);
 setTimeout(function() { o.start(); }, 1000);
Chetan S
  • 23,637
  • 2
  • 63
  • 78
  • I am using setTimeout: `var listen = new Request(destination, stay_open); setTimeout(listen.start, 500);` – Carson Myers Oct 25 '10 at 04:01
  • This saved my life, upon trying to understand why the function I was passing to express' [basicAuth](https://www.npmjs.com/package/express-basic-auth) was not working with the same output. – Edison Spencer May 22 '20 at 12:23
  • Or do `o.start.bind(o)`. Why doesn't `x = o.start; x()` work? – theonlygusti Nov 12 '20 at 14:14
  • `.bind()` returns a new function, not working in-place. So you'd have to do `x = o.start.bind(o); x()` or `o.start = o.start.bind(o); x=o.start; x()` – ceedob Dec 07 '20 at 13:53
40

None of the previous answers had the full solution for me, so posting mine here.

I had a class, which was returning an error when I ran forEach on the method reference.

e.g.

class Foo {
  hello (name) {
    return `hello ${name}`
  }

  doGreet (name) {
    return console.log(this.hello(name)) // <- 'this' is undefined
  }
}

// print some names...
const foo = new Foo();
(['nick', 'john']).forEach(foo.doGreet)

// TypeError: Cannot read property 'hello' of undefined
//     at doGreet (/.../test.js:7:17)

The solution was to the bind the context of the method's this within a constructor. i.e.

class Foo {
  constructor () {
    this.doGreet = this.doGreet.bind(this) // <- Add this
  }

  hello (name) {
    return `hello ${name}`
  }

  doGreet (name) {
    return console.log(this.hello(name))
  }
}
Daniel Tonon
  • 9,261
  • 5
  • 61
  • 64
Nick Grealy
  • 24,216
  • 9
  • 104
  • 119
  • 1
    PSA: I tried to do a bunch of shortcuts to automate this or make it less cumbersome. Unfortunately `Object.keys(this)` inside the constructor wasn't able to find all the keys on the object. The best I could do was this: `const bind = (key) => { this[key] = this[key].bind(this) }` Then call `bind('hello'); bind('doGreet')` for each method name manually :( – Daniel Tonon Feb 26 '22 at 08:40
  • @DanielTonon [See my answer :)](https://stackoverflow.com/a/72197516/8374115) – Walter Woshid May 11 '22 at 08:07
  • 1
    @WalterWoshid nice solution! Will give it a go. – Nick Grealy May 11 '22 at 12:34
  • Allelouia ! That works ! Thank you so much ! – bendeg Jun 08 '22 at 08:31
  • 1
    You're a lifesaver, man javascript is stupid sometimes – Shardj Nov 23 '22 at 16:19
  • How would you accomplish this if `doGreet` is a private method (`#doGreet`)? I get an error saying private methods are not writable. – Martin_W Mar 07 '23 at 20:29
  • @Martin_W javascript doesn't have a `private` accessor keyword... can you share a code snippet? – Nick Grealy Mar 07 '23 at 23:06
  • Example: `class Example { #privateFunc(arg1) { return \`Hello ${arg1}!\`; } method1() { this.#privateFunc = this.#privateFunc.bind(this); const localFunc = this.#privateFunc.bind(this); console.log('This is only a test.'); } }`. Attempting to assign to `this.#privateFunc` will fail, but assigning to `const localFunc` is fine. – Martin_W Mar 08 '23 at 15:31
  • Private class methods are explained here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields#private_methods – Martin_W Mar 08 '23 at 15:37
  • Looks like a solution for private functions is to define a "shadow" private function and combine it with a private variable: `class Example2 { constructor() { this.#privateFn = this.#shadowFn.bind(this); } #privateFun = null; #shadowFn(arg1) { /* actual function code */ return \`Hello ${arg1}!\`; } }`. – Martin_W Mar 08 '23 at 15:48
  • Thanks @Martin_W - today I learned something new! – Nick Grealy Mar 08 '23 at 15:53
18

JavaScript's OOP is a little funky (or a lot) and it takes some getting used to. This first thing you need to keep in mind is that there are no Classes and thinking in terms of classes can trip you up. And in order to use a method attached to a Constructor (the JavaScript equivalent of a Class definition) you need to instantiate your object. For example:

Ninja = function (name) {
    this.name = name;
};
aNinja = new Ninja('foxy');
aNinja.name; //-> 'foxy'

enemyNinja = new Ninja('boggis');
enemyNinja.name; //=> 'boggis'

Note that Ninja instances have the same properties but aNinja cannot access the properties of enemyNinja. (This part should be really easy/straightforward) Things get a bit different when you start adding stuff to the prototype:

Ninja.prototype.jump = function () {
   return this.name + ' jumped!';
};
Ninja.prototype.jump(); //-> Error.
aNinja.jump(); //-> 'foxy jumped!'
enemyNinja.jump(); //-> 'boggis jumped!'

Calling this directly will throw an error because this only points to the correct object (your "Class") when the Constructor is instantiated (otherwise it points to the global object, window in a browser)

xj9
  • 3,381
  • 2
  • 24
  • 23
10

Use arrow function:

Request.prototype.start = () => {
    if( this.stay_open == true ) {
        this.open({msg: 'listen'});
    } else {

    }
};
Kasra
  • 1,959
  • 1
  • 19
  • 29
7

In ES2015 a.k.a ES6, class is a syntactic sugar for functions.

If you want to force to set a context for this you can use bind() method. As @chetan pointed, on invocation you can set the context as well! Check the example below:

class Form extends React.Component {
constructor() {
    super();
  }
  handleChange(e) {
    switch (e.target.id) {
      case 'owner':
        this.setState({owner: e.target.value});
        break;
      default:
    }
  }
  render() {
    return (
      <form onSubmit={this.handleNewCodeBlock}>
        <p>Owner:</p> <input onChange={this.handleChange.bind(this)} />
      </form>
    );
  }
}

Here we forced the context inside handleChange() to Form.

Nitin
  • 7,187
  • 6
  • 31
  • 36
  • 9
    You should bind the function to this in the constructor. Otherwise you're binding it every time `render` is called instead of once when the class is instantiated. Also, most of your example is not really relevant to the question. – Eric Haynes Apr 20 '18 at 02:10
  • Or use the arrow syntax when defining the `handleChange()` – Nitin Aug 24 '20 at 22:35
4

This question has been answered, but maybe this might someone else coming here.

I also had an issue where this is undefined, when I was foolishly trying to destructure the methods of a class when initialising it:

import MyClass from "./myClass"

// 'this' is not defined here:
const { aMethod } = new MyClass()
aMethod() // error: 'this' is not defined

// So instead, init as you would normally:
const myClass = new MyClass()
myClass.aMethod() // OK

Oli
  • 852
  • 6
  • 12
4

If a function has been used as a high order function (passed as an argument) the scope of this gets lost.

To fix this problem one can bind this to the function like @Eliux described:

this.myFunction.bind(this);

To automate this process like @DanielTonon wanted you can do this in the constructor:

Object.getOwnPropertyNames(YourClass.prototype).forEach((key) => {
  if (key !== 'constructor') {
    this[key] = this[key].bind(this);
  }
});
Walter Woshid
  • 329
  • 2
  • 11
0

I was getting this error while passing a catch-handler for a promise.

class foo {

   catchHandler(error){
     // do something with this
   }
  
   async myFunction(){
       let x = await somePromise.catch(this.catchHandler);
   }
}

I was trying to bind the catchHandler-Function to this and i still got the error. What finally fixed it for me was not just pass the function as an argument but using a arrow-Function as a 'wrapper':

class foo {

   constructor(){
     this.catchHandler.bind(this); // <--
   }

   catchHandler(error){
     // do something with this
   }
  
   async myFunction(){
      --> let x = await somePromise.catch(e => this.catchHandler(e)); // <--
   }
}

This might not help OP but when you encounter the problem as stated in the Question-Header

Raqha
  • 754
  • 4
  • 17