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?

1

2 Answers 2

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);
Sign up to request clarification or add additional context in comments.

3 Comments

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.
FWIW, jQuery also has $.extend, but I do agree, Underscore is a nice library. Luckily, OO JS is not too much of a common thing for me.
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.
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.