2

When running this code, the whatever value is first passed in with the constructor Test(callbacks) becomes the callback that is always called, even in later instantiations of Test

function Test(callbacks) {
    if (callbacks) {
        if (callbacks.callback) {
            this.callback = callbacks.callback;
        }
    }

    this.options.complete = $.proxy(this.options.complete, this);
}

Test.prototype = {
    options: {
        type: "GET",
        complete: function() {
            this.callback();
        }
    },
    callback: function() { console.log("OVERRIDE ME"); },

    execute: function() {
        $.ajax(this.options);
    }
};

var eins = {callback: function() {console.log("AAA");}};
var zwei = {callback: function() {console.log("BBB");}};

var A = new Test(eins);
var B = new Test(zwei);

A.execute();
B.execute();

Running this code, every time you will get the output AAA. How does function() {console.log("AAA");} become a constant value for the prototype?

  • possible duplicate of [Crockford's Prototypal inheritance - Issues with nested objects](http://stackoverflow.com/questions/10131052/crockfords-prototypal-inheritance-issues-with-nested-objects) or [Why are my JS object properties being overwritten by other instances](http://stackoverflow.com/questions/13127589/why-are-my-js-object-properties-being-overwritten-by-other-instances) – Bergi Dec 13 '12 at 20:39

2 Answers2

1

It all starts with this line:

this.callback = callbacks.callback;

When you make a call new Test(eins), eins comes in as the callbacks argument. That line then sets this.callback (ie. the "callback" property on your new instance of Test) to the callback property of callbacks, ie. of eins.

Now, that alone wouldn't affect B. However, there's something tricky:

this.options.complete = $.proxy(this.options.complete, this);

You would think that that would set the "options" property on your Test instance, right? Wrong. The way Javascript works is that if a property isn't defined on your instance (eg. you didn't do this.options = something) then Javascript will look up the "prototype chain", where it will find the prototype's "options", and set it (not your instance's "options", since your instance doesn't have one).

You can fix all this by changing that line to:

this.options = {complete: $.proxy(this.options.complete, this)};

but of course that would lose your type: "GET", so either you need to do:

this.options = {type: "GET", complete: $.proxy(this.options.complete, this)};

or you need to base your options off the prototype's:

this.options = {};
for (var key in this.prototype.options) {
    this.options[key] = this.prototype.options[key];
}
this.options.complete = $.proxy(this.options.complete, this);

If you happen to be using the (excellent) Underscore library, it even has an extend function for doing this sort of thing more easily:

this.options = _.extend({}, this.prototype.options,
                        {complete: $.proxy(this.options.complete, this)});

Or (depending on your style preferences):

this.options = _.extend({}, this.prototype.options);
this.options.complete = $.proxy(this.options.complete, this);

Incidentally, Underscore also has a _.bind method which is comparable to jQuery's "proxy", so you could also do:

this.options = _.extend({}, this.prototype.options);
this.options.complete = _.bind(this.options.complete, this);
machineghost
  • 33,529
  • 30
  • 159
  • 234
  • I never said it wasn't :-) The question was "How does function() {console.log("AAA");} become a constant value for the prototype?", and the answer is that it happens in that line. – machineghost Dec 13 '12 at 20:28
  • FWIW, jQuery also has [`$.extend`](http://api.jquery.com/jQuery.extend/), but I do agree, Underscore is a nice library. Luckily, OO JS is not too much of a common thing for me. –  Dec 14 '12 at 13:02
  • There's some subtle difference between the jQuery extend and the Underscore one, but I couldn't remember what that difference was so I stuck to Underscore's in my example. But yeah, you can totally use jQuery's too. – machineghost Dec 14 '12 at 19:10
0

When you do

    this.options.complete = $.proxy(this.options.complete, this);

You're replacing the options.complete function of the prototype by one that will always have this as context.

The problem is that you don't have a this.options object proper to this, but only one shared with all objects having the same prototype.

Denys Séguret
  • 372,613
  • 87
  • 782
  • 758