user372743
user372743

Reputation:

Javascript Constant Setting Variable

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?

Upvotes: 2

Views: 323

Answers (2)

machineghost
machineghost

Reputation: 35760

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);

Upvotes: 1

Denys Séguret
Denys Séguret

Reputation: 382102

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.

Upvotes: 0

Related Questions