3

I'm trying to use jQuery.fn.extend to override jQuery.fn.init with my own implementation which is going to behave differently from the original implementation but is going to need to call the original implementation at certain places to do the real work.

So, the very first thing would be to actually "proxy" for the original implementation in the new one. That's what I'm attempting to do and I'm observing what seems like awkward behaviour (this seems like one of those JS things).

You can see my latest attempt here. So the problem is. It is only supposed to apply the border to the .parent .child. Not both .parent and .child. That's what it seems to be doing right now. If you remove my jQuery.fn.extend call. You can see the original, correct behavior.

So, the question is what am I doing wrong? Is this the right approach to proxying any JS function and specially the jQuery init function? Is there a better way?

Extra

I saw this old question and the answer there refers to jQuery.sub which has been moved to the jQuery Migrate plugin now. Should I try to use that? Is it any more likely to work than what I'm trying right now? Do I really need to?

Community
  • 1
  • 1
SBhojani
  • 499
  • 1
  • 4
  • 19
  • It is supposed to get one border not two. That's the problem. – SBhojani Aug 17 '13 at 02:39
  • @Quantas, I was going to comment this on your answer: *You nailed the cause, but your proposed solution won't work for the use case in the OP. I believe you have to forward the arguments, something like `var jq = new oldInit(); return oldInit.apply(jq, Array.prototype.slice.call(arguments));`. http://jsbin.com/ECOTEFO/1/edit* – bfavaretto Aug 17 '13 at 03:41
  • This seems to work too: `var jq = Object.create(jQuery.fn); return oldInit.apply(jq, arguments);` – bfavaretto Aug 17 '13 at 03:44
  • @bfavaretto: on the money there, seems we came to the same solution independently. – Qantas 94 Heavy Aug 17 '13 at 04:08
  • @Qantas94Heavy Yup, I was considering writing an answer when you posted yours. But this is what I'm still puzzled about: if I `console.log(arguments)` inside init, I see 4 logs, including 2 with `undefined` as the first argument. I'm afraid this solution may be causing extra calls to the jQuery function. http://jsbin.com/IHOLoBo/1/edit – bfavaretto Aug 17 '13 at 04:22
  • @bfavaretto: When removing the call to `jQuery` at the end, there's still a call for some reason... – Qantas 94 Heavy Aug 17 '13 at 04:37
  • I think it's doubling the calls: for each call to jQuery, it first creates a new jQuery object (call #1), then applies the original init with the arguments (call #2). But maybe that's no big deal. @Qantas94Heavy – bfavaretto Aug 17 '13 at 04:41
  • @bfavaretto: (if I'm right) it's normal behaviour, hidden deep inside the jQuery source code. Anyway let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/35639/discussion-between-qantas-94-heavy-and-bfavaretto) – Qantas 94 Heavy Aug 17 '13 at 04:52

1 Answers1

1

Note: you really shouldn't be doing this, as internal jQuery code also calls the constructor, which could bring about very unusual issues if you aren't extremely careful.

Your problem is that you aren't calling oldInit as a constructor - rather as a function, which doesn't really work because anything set inside jQuery.fn.init will go on jQuery.fn, rather than a new jQuery object.

Why doesn't just setting the ThisBinding to {} work then?

Although this can seem somewhat intuitive after learning about the way the "new" operator works, this doesn't actually do the same thing. Why?

function Foo() { this.bar(); }
Foo.prototype.bar = function () { alert(1); };
new Foo; // 1 is alerted
Foo.apply({}); // TypeError: Object #<Object> has no method 'bar'

When new is used, it also gives the instance's __proto__ the constructor's prototype object. When you create an object literal, this is the standard Object.prototype, not the intended prototype object.

What to do then?

If you are attempting to override jQuery.fn.init, you need to use something like this:

var oldInit = jQuery.fn.init;
jQuery.fn.extend(
{ init: function ()
    {   return oldInit.apply(new oldInit, Array.prototype.slice.call(arguments));
    }
});

How does this work?

By calling new oldInit with no arguments, we'll just get back an empty object with __proto__ set to jQuery.fn, exactly what we want. Then, we'll supply the arguments to oldInit, and any arguments will go straight to this empty new object, just like the original.

This works because when you call it, your actual ThisBinding (the value of this inside the function call) will already be the new jQuery object, because you are meant to refer to this to add new properties to an instance of the jQuery.fn.init constructor.

Your original code was the following:

var oldInit = jQuery.fn.init;
jQuery.fn.extend({
    init: function () {
        return oldInit.apply(jQuery.fn, Array.prototype.slice.call(arguments));
    }
});

That would make your constructor think that the new function is meant to be jQuery.fn, which is probably not what you had intended. If you had a standard function, that would work (as that is what the ThisBinding is meant to be), however since the "new" operator changes the ThisBinding, this no longer means the same thing as before.

Qantas 94 Heavy
  • 15,750
  • 31
  • 68
  • 83