Skip navigation.

exploreopera

| Help

Sign up | Help

interesting jQuery stuff

, , ,

The other day (well, night actually) I was looking at a problem on a site that used the jQuery library. Though I've heard of it, I haven't seen jQuery on live sites before and I thought it shows off some quite interesting features of JavaScript.

The latest version of the library is available so you can see what I'm talking about.

A function can call itself recursively as constructor

Just inside the jQuery function we find:

if ( window == this )
return new jQuery(a,c);


This clever little trick means you don't need to use the "new" keyword all over your code to create a new jQuery object. You can just do
var obj=jQuery()
and the this check will detect that it wasn't called as a constructor and call itself recursively, this time using the "new" keyword to define the expected jQuery object.

I have a question about the if clause though - it presumably means trouble if you change the "this" object and use something else than window. Say,
var obj = jQuery.call(document)
would probably break something. I don't know jQuery well enough to tell if that would be a problem, but the check could perhaps be
if(this.constructor!=arguments.callee)
to catch all cases?

Using || operator to default to an argument inside a function call

You may be used to seeing the || operator being used to provide a default value for a variable. For example
var username=prompt('Your name please')||'anonymous'
will set the variable to "anonymous" if the user cancels the prompt or doesn't type any value.

It still took me a while to understand what is going on here:

return this.setArray(
// HANDLE: $(array)
a.constructor == Array && a ||

// HANDLE: $(arraylike)
// Watch for when an array-like object is passed as the selector
(a.jquery || a.length && a != window && !a.nodeType && a[0] != undefined && a[0].nodeType) && jQuery.makeArray( a ) ||

// HANDLE: $(*)
[ a ] );


The basic idea is: if the object a is an array, we pass it to the function directly. If it is an object that is somewhat similar to an array (for example a NodeList), we pass the result of calling the jQuery.makeArray function which will take all the elements/nodes and add them to an actual array. Otherwise, we just pass an array containing nothing but the object a.

The key to understanding this is to understand that the statement
a.constructor == Array && a
actually "returns" a if the first comparison is true. It is probably better phrased as "evaluates to a" but it may be easier explained as "returns".

Similarly,
(a.jquery || a.length && a != window && !a.nodeType && a[0] != undefined && a[0].nodeType) && jQuery.makeArray( a )
will go through all the conditions, and if they all hold true return the result of calling the makeArray function.

The || operator ties it all together and ensures the first clause that evaluates to something is chosen as input to the function.

You can use Array.push to add array elements to any object

The next interesting snippet is inside the setArray function itself:
setArray: function( a ) {
this.length = 0;
[].push.apply( this, a );
return this;
}


Look at [].push.apply( this, a );. What happens here is that the array literal's "push" method is used to push element a onto the this object which isn't an array but a jQuery object. That's right, push is generic and can be used with any object. It means the object gets a ".length" property like an array has and can be iterated with for loops. Example:

var obj = new Object(); /* Yes: an object, not an array. */
var str='';
Array.prototype.push.call(obj, 'Hello');
Array.prototype.push.call(obj, ' World');

for(var i=0;i<obj.length;i++){
str+=obj[i];
}
alert(str);


Array methods being generic is part of the amazing flexibility of JavaScript, but this feature of the language might not be well known among authors.

About the jQuery code, I wonder if their approach of creating an empty array literal to use its push method is slower or faster than my Array.prototype lookup. Perhaps the question will inspire a reader to do some performance testing?

real *nix devs don't test in IEwindow.opener and security - an unfixable problem?

Comments

avatar
Array.push doesn't exist in IE 5.0...
Edit: but seems that they don't support anything lower than IE 6.

jQuery test results:
  • Opera 9.20(8713): 14 tests of 430 failed
  • Opera 9.20(8732): 9 tests of 426 failed
  • Opera 8.54: 21 tests of 430 failed
  • Firefox 2: 4 tests of 424 failed
  • IE 7: 10 tests of 426 failed
  • IE 6: 10 tests of 426 failed
  • IE 5.5: 30 tests of 423 failed
  • IE 5.0: error


By FataL, # 9. March 2007, 16:47:12

avatar
BTW, on that test suite: I get only 9 failing tests in 9.20 (happen to be in build 8720 though). 2 of them happen to be this issue: http://my.opera.com/community/forums/topic.dml?id=179820
Many of the other ones fail the same way in Firefox so might be an error in the tests.

By hallvors, # 9. March 2007, 17:54:25

avatar
About the "window == this" check: Until someone brings up a case where you need to do jQuery.call(), the current check should be fine.

And about the testsuite: It is also part of the bug tracking. When I can't solve a particular bug I investigated, I leave the test for others to refer to.

The list of supported browsers is close to YUI's list of A-grade browser. It's pretty difficult to support more then those without bloating the library even more with tons of workarounds.

I'm curious if Opera 9.20 fixes a particular bug I have with select-elements. I've got a testsuite for my validation jQuery plugin that shows that error: http://jquery.bassistance.de/validate/demo-test/testx.html

Regards
Jörn Zaefferer

By Enchos, # 9. March 2007, 18:09:24

avatar
Hi Jörn,
I was just being curious about the window==this bit. I don't know jQuery well enough to be anywhere near suggesting a use case for jQuery.call() :smile:

Regarding the SELECT element issue from your test page, that is caused by Opera's WebForms2 implementation and thus isn't a bug. Have a look at this:

http://www.whatwg.org/specs/web-forms/current-work/#selectSeeding

By hallvors, # 9. March 2007, 21:56:57

avatar
(Do you happen to have the Opera bug number? I found no bugs filed for that URL..)

By hallvors, # 9. March 2007, 22:01:28

avatar
if(this.constructor!=arguments.callee) won't work when the object has prototyped methods. I personally use if(this.constructor != Object) but then obviously this could also fail in some cases...

By crisp, # 11. March 2007, 00:11:03

avatar
if(this.constructor!=arguments.callee) won't work when the object has prototyped methods.
Could you please elaborate? Which object exactly? Can you provide an example to test?

By _Grey_, # 11. March 2007, 02:31:49

avatar
_Grey_:

function Foo()
{
if(this.constructor!=arguments.callee)
alert('Not called as a constructor!');
else
alert('Called as a constructor!');
}

Foo();
new Foo();

Foo.prototype =
{
bar: function()
{
alert('something');
}
}

Foo();
new Foo();

By crisp, # 11. March 2007, 22:25:59

avatar
@crisp:

After some thought (and some more experiments) I came up with the overcomplicated condition
if(this==window || (this.constructor!=Object && this.constructor!=arguments.callee))

This fits your case (cross-browser), but it still doesn't cover this case:
Foo.call({})


This would perhaps be best?:
function Foo(x)
{
if(x!='construct!')
{
if(arguments.length==0)
return new Foo('construct!');
else
//main function, if applicable
}
else //construct something
{
this.bar = 'baz';
return this;
}
}

f = Foo();
g = new Foo();

alert(f.bar);
alert(g.bar);

By _Grey_, # 12. March 2007, 04:27:16

avatar
Well, meanwhile I have come to the conclusion that it is probably just not a good idea to completely overwrite the prototype property of an object P:

By crisp, # 13. March 2007, 07:45:30

avatar
crisp: do you know what in the ECMAScript spec causes the behaviour you've demonstrated? I'm just curious.. the way it works looks pretty confusing.

By hallvors, # 14. March 2007, 15:49:35

avatar
@hallvors: I guess it's because this.constructor doesn't exist. Thus it is chained-on from the prototype. And the constructor of {} is by default Object().

@crisp: Well, as long as you keep the right constructor... :wink: I think you hit the point, though. If you do replace the prototype, make sure it works without breaking.

By _Grey_, # 18. March 2007, 23:55:53

avatar
Indeed, constructor is prototyped itself from prototype. I've switched to prototype's (the library) extend() method to circumvent overwriting an objects prototype and thus constructor :smile:

By crisp, # 22. March 2007, 00:04:06

avatar
@crisp: What for? Just for the convenience of using object-syntax? I don't see the need to replace a prototype unless you can create a prototype that doesn't inherit from Object.

By _Grey_, # 22. March 2007, 00:31:14

avatar
_Grey_: object-syntax is more 'clean' imo, so instead of doing:

Foo.prototype.bar = function() {}
Foo.prototype.woei = function() {}

I do prefer something along:

Object.extend(Foo.prototype,
{
bar: function() {},
woei: function() {}
});

By crisp, # 26. March 2007, 00:38:08

avatar
So you agree it's just syntactic sugar? :wink:

By _Grey_, # 26. March 2007, 03:31:13

avatar
Yes; but it's also less verbose so imo easier to read :smile:

By crisp, # 29. March 2007, 21:56:37

Write a comment

You must be logged in to write a comment. if you're not a registered member, please sign up.