1

If we have the following class (a simplified example):

function MyClass() {
    this.greeting = "Where is the ice cream?";
}

MyClass.prototype.talk = function() {
    console.log(this.manners(this.greeting));
}

MyClass.prototype.manners = function(phrase) {
    return "I beg your pardon. " + phrase;
}

And then we attempt to send it through ajax as follows:

var c = new MyClass();
$.ajax({
    data : c,
    url : "ajax.php",
    dataType : "json",
    type : "post"
});

I get an exception from MyClass.prototype.talk, saying that "this.manners is not a function". The reason I get this exception is that, for some reason, the ajax call is forcing all of the methods on MyClass to be called, with a this of Window (rather than an instance of MyClass).

Accordingly, I have a couple of questions:

  • Why does the ajax call force every method of the class to be called with Window for this?
  • Is it not appropriate to call manners from talk in the way that I am? Should I be setting this myself? (not sure how that's possible in this context)

In general, I am still quite ignorant about how and when to use apply and call, so this whole topic is very confusing to me. Whenever I've gotten in trouble in the past with such issues (e.g., in event-handling), I've resorted to the var that = this; work-around, but I think it's time to understand the topic more in-depth. Besides, that fix doesn't work here anyway.

EleventyOne
  • 7,300
  • 10
  • 35
  • 40
  • How do you expect instance's methods to be posted? – Artyom Neustroev Sep 29 '13 at 19:02
  • I guess I wouldn't have expected them too. In the past, to get around this situation, I simply added a method called `toLiteral()` which returned all of the members in a separate object - and then set `data : c.toLiteral()`. That worked fine, but I worry that I'm missing the "correct" way to do things when it comes to issues involving `this`. – EleventyOne Sep 29 '13 at 19:06
  • I highly recommend Secrets of the Javascript Ninja -- it goes into *this* and the 4 ways functions are invoked quite in-depth in an early chapter. Co-authored by John Resig, creator of jQuery. – Faust Sep 29 '13 at 20:39

3 Answers3

1

EDIT: Whilst the methodology is correct, prototypes don't inherit local variables, stingify looks like the best route.

To answer questions: 1. 'this' always reflects the current context and AJAX resorts to a global scope thus resulting in window. 2. Is it appropriate? Probably not. I would be inclined to use it in the following way:

var c = new MyClass();
c.greeting = 'You ate all the ice cream!';
c = c.manners(c.greeting);
$.ajax({
    data : c,
    url : "ajax.php",
    dataType : "json",
    type : "post"
});

Suggestion below won't work in this instance:

Off the top of my head; explicitly set 'this' in myClass()

var _that = this;

... replace 'this' with _that in your prototype methods

Scope is lost during an AJAX request and reverts to window. Defining _that = 'this' at the top of your class always ensures scope stays intact.

Damien Wilson
  • 315
  • 3
  • 9
  • Forgive me, but where would you put the `var _that = this;` statement? Surely I wouldn't want to create a global variable, but I don't see how else I could get your suggestion to work. – EleventyOne Sep 29 '13 at 19:33
  • On reflection I wouldn't use my suggestion. As you so rightly point out you don't want to create a global instance of the var. The solution would be to normalise data from your class before sending through an AJAX request. Such as @Jackson suggestion below: JSON.stringify() – Damien Wilson Sep 29 '13 at 19:36
1

I think you need to serialize your data.

data : JSON.stringify(c),

It seems to POST now: http://jsfiddle.net/JvPym/

I don't think that there were any problems with this or window.

Jackson
  • 9,188
  • 6
  • 52
  • 77
  • Yes, it does seem that this is the only way to avoid the problem in this particular instance. – EleventyOne Sep 29 '13 at 23:16
  • Methods won't serialise in JSON. JSON is only data but the instance variable `c` of MyClass has behaviour too, this is not sent. I don't know how to serialise JS object with behaviour and I think you can't but guess sending the data alone is good enough. – HMR Oct 01 '13 at 03:51
0

One thing to know about javascript, is that it doesn't have classes. Javascript has objects and functions (well functions are just objects).

The this variable is attached to functions, and follows the "invocation pattern". this is given a certain value depending on how you invoke a function. All function objects have a call and apply method which execute the function object they're called on. The first parameter to these methods specifies the object which will be referenced as the this context during execution of the function, however if it's null or undefined, the global object window, is used.

the best way to overcome this is to create an internal variable in the function that references this later on in the correct context. Below is an example on how it could be rewritten:

function MyClass() {
        var _self = this;  
        this.greeting = "Where is the ice cream?";


    this.talk = function() {
        console.log(_self.manners(this.greeting));
    }

    this.manners = function(phrase) {
        return "I beg your pardon. " + phrase;
    }
}

Notice how _self if used to reference the correct context. In your functions, you would only reference _self instead of this.

TheDaveJay
  • 753
  • 6
  • 11
  • But as highlighted above, by Damien, this would preclude you from using `prototype`. – EleventyOne Sep 29 '13 at 19:42
  • in that case, in IE9+ you can use the `Function.prototype.bind` to solve that problem. More can be found [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FFunction%2Fbind) – TheDaveJay Sep 29 '13 at 20:11
  • Hmmm, I recall messing around with binds in underscore, but it's not clear to me how that approach could be used to solve the specific problem posed in the question. – EleventyOne Sep 29 '13 at 20:14
  • Have a look at the "Bound functions used as constructors" section. Im pretty sure that can solve your problem. If you dont come right, I will give you a hand. – TheDaveJay Sep 29 '13 at 20:18
  • It starts out by saying: "The methods shown below are not the best way to do things and probably should not be used in any production environment". I'm trying to learn best practices, as well as learn about `this` in the context of the posted question. – EleventyOne Sep 29 '13 at 20:22
  • JavaScript has prototype and you're not using it. This may be of interest for you: http://stackoverflow.com/a/16063711/1641941 To get the invoking object there are better ways than throwing out prototype: http://stackoverflow.com/a/19068438/1641941 – HMR Oct 01 '13 at 03:46
  • And that doesn't answer the question about how to serialise the object so you can POST it in a xhr request. – HMR Oct 01 '13 at 03:49
  • @EleventyOne You may find the links I just posted interesting. How to inherit, override, calling super and the other one on invoking object and what `this` can mean in JavaScript. – HMR Oct 01 '13 at 03:55