miscoded

the web is a hack

Subscribe to RSS feed

Posts tagged with "jQuery"

jQuery relies on function decompile, won't work on Opera mobile

, , ,

A colleague just noticed that the otherwise excellent jQuery JS library has an "isFunction" method that relies on decompiling functions to figure out if they really, really are functions. This is not a good idea because decompiling functions is an optional feature of the ES-262 spec. It is slow and requires too many resources for certain low-end platforms, so it is not supported by any of the Opera Mobile versions.

Here's the code, complete with a frustrated comment from John Resig himself:
// This may seem like some crazy code, but trust me when I say that this
// is the only cross-browser way to do this. --John
isFunction: function( fn ) {
	return !!fn && typeof fn != "string" && !fn.nodeName && 
		fn.constructor != Array && /function/i.test( fn + "" );
}


John, I feel your pain and that code clearly took quite some effort and testing across various browsers passing in various types of objects - IE collections allowing ()-reference, anyone? It wouldn't surprise me if Opera is one of the culprits that caused you problems here since we try to support some of IE's weirdness and hence "typeof" sometimes returns 'function' when you'd expect it not to.

So I'm sorry to break it to you, but /function/i.test( fn + "" ) won't do what you think on Opera's mobile versions, causing really-hard-to-track-down bugs.

May I suggest adding the below workaround somewhere?
if( (function(){}).toString().match(/\^[ecmascript/i) ) Function.prototype.toString = function(){return 'function'};


Should be all you need..

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?