2012.12.20

My jQuery Wish List

Haters gonna hate but I’ve been delaying for too long to write this one. Since the jQuery team is working to get the version 2.0 out of the door and Christmas is coming it might be a good time to talk about it. Please don’t add comments like “use framework/library X instead!”, the question here is not about which framework/library is better but about things that I think could be improved.

I know that jQuery 2.0 will have the same API as 1.9, this wishlist would be for a future version of jQuery that doesn’t need to be backwards compatible (call it jQuery Harmony or jQuery 3.0 if you will).

Consistency

I used jQuery on most projects for the past 5 years and I still get confused with some methods. I think that a good part of it is because some methods/options are poorly named and also because most functions have a LOT of faux-overloads. The fact that every single method is in the same namespace and that everyone thinks that everything should be a plugin, amplifies this problem as well.

add()

What would you expect when you call a method add() on a collection? My initial thought would be that it append the new elements to the end of the collection, just like Array#push does. Of course if the collection is a jQuery collection I would be wrong.


var lists = $('ul');
var allLists = lists.add('ol');
console.assert( lists === allLists ); // false, it returns a new collection!

Since it returns a new collection concat or join would be better names.

map() / each() / filter()

Arguments order is reverse than what I would expect, there are native Array methods with similar names and different behavior.


// how it is
$('.foo').each(function(index, element){
    ...
});
// how I expect it to be
$('.bar').each(function(element, index){
    ...
});

I rarely need the index but I need a reference to the element 99% of the times. And no, don’t tell me to use this to reference the element (I avoid it).

It should also accept a 2nd argument to set the context (this keyword) value, just like the ES5 Array methods.

is() / not() / has()

Ok, this one used to get me all the time. Almost all methods inside the traversing category on the documentation returns new collections, they are basically ways to filter the matched elements, grab siblings/parent elements, add new elements to the collection – basically anything that changes the matched elements – but of course there are exceptions to the rule.

By looking at the names is, not and has you would expect the return value from the 3 methods to be similar, guess what? they aren’t. is() returns a boolean, not() and has() filters the collection. Very counter intuitive.

not() is the opposite of filter(), by looking at the name I would expect it to be the opposite of is(). Better name for not() would be reject(), exclude(), discard(), etc…

I also think that has() gives an idea that the return value will be a boolean: “does X has Y?”. This one might need a two word name to be descriptive, even a bad name like ifHas() would solve the return ambiguity. The method has() should be similar to is(), it should return true if any of the matched elements have a descendant that matches the selector or maybe even better, should be called hasDescendant().

contents() / children()

I’m not a big fan of method overload and I also don’t like booleans as arguments (boolean trap) but this is the case where I think a boolean on children() would be better than a new method named contents(). I have no idea what contents returns, is it a string? does it require any arguments? is it a setter or getter or both? Name it getChildren() and getAllChildren(), problem solved.

Better names

Descriptive names can reduce the need of a documentation, it also makes the API discovery process way easier, you can guess what the method does just by looking at the name. Besides the lack of consistency on the previous examples jQuery also contains many methods that are poorly named. Just to cite a few:

jQuery.inArray()

Why? I don’t even… A method called inArray returns an integer (-1 if not found), this should be called indexOf or return a proper boolean value, no excuses for it.

jQuery.proxy()

This method should be called bind since it is similar to the ES5 Function#bind, proxy is an overloaded term in programming (design pattern, proxy server, …) and should not be used to describe something that have a popular/alternative name.

jQuery.extend()

You are not extending an object, you are augmenting it with properties from another one. This is a helper for mixIns. mixIn or merge would be better.

toggle()

Toggle what? class name? display? visibility? state? Is this method really needed?

jQuery.param()

This name isn’t really descriptive; query, objectToQuery, serialize would be better. Note that serialize would conflict with a existing method but that method should also have a better name (serializeForm, serializeFormAsArray, formToQuery).

$.Callbacks

Callbacks are a dumbed down version of a Signal with a few mistakes.

Flags being a string is error prone and awkward.


// ops, typo and multiple spaces. silent failure.
var cb = $.Callbacks('once  memoy');

fireWith() is weird since all the callbacks will be called on the same context and it can cause conflicts, you need to know the structure of all the callbacks beforehand and it deceives the decoupling logic, the event dispatcher shouldn’t know about the handlers implementation… Setting context during add() would be better.

disable() and lock() seems redundant and doesn’t have options to re-enable/unlock.

Callbacks should be replaced by js-signals, better API, better implementation, more flexible and less error prone.

$.Deferred

Follow the Promises/A+ spec. kthx, bye.

Animation

Make it better, faster, stronger. The queue system is nice in some cases but most of the times it is a huge PITA. Multiple consecutive calls trigger multiple animations, I don’t think we should need to call stop(true) every time we want to toggle the animation. If you don’t know what I mean just check this jsbin.

I also think it should provide an easy way to reuse the methods without triggering all the hooks so I could tween any numeric values without overhead. There is no reason why the animation module should be tight coupled with the DOM.

This is a part of jQuery that I would probably throw away and start from scratch.

Ajax

Please fix this. Too many options and some really bad names, I always need to check the documentation to use it and I always commit mistakes that are hard to track, mostly due to misspelled/invalid options.

Just look at the documentation, 33 options! Nobody will ever remember all of them, that is fine, as long as the frequently used ones are easy to guess and if I pass an invalid option it tells me that I’m wrong, unfortunately that is not the case.

data sets which arguments should be sent together with the request (query string or POST body), so why dataType and dataFilter are related to the response? contentType is used to set the request content type. contents is used to parse the response… Please keep names consistent, if you use a prefix/word for the request don’t use it as well for the response.

If I had a penny for each time I try to use method: "POST" instead of type: "POST" I would have almost $1. In my head GET, POST, PUT, DELETE are request methods, not types.

ajaxError / ajaxSend / ajaxStop / ajaxError

These should be signals/$.Callback so you could add/remove listeners, set priority, etc:


jQuery.on.ajaxError.add(logAjaxError);
// later in the code we can remove listener
jQuery.on.ajaxError.remove(logAjaxError);

I rarely use this, but sometimes it can be useful for logging and/or for generic error handling. For debugging Charles Proxy and Dev Tools Network Panel are way more useful.

Less method overloads

Some jQuery methods have more than 5 overloads based on the arguments that are passed. Having optional parameters and accepting a couple different arguments types can make things simpler but it surely shouldn’t be abused. The jQuery() method is the biggest offender, you can pass selectors, elements, object, jQuery instance, function, HTML string… – This should be split into at least 3 distinct methods, being brief is not always a good thing.

My point is that it is harder to read the code afterwards if you aren’t familiar with the different signatures of the method. Discovering the API is also harder and requires constant checks on the documentation.

Modularity

The custom build (added on v1.8) was a good step forward but still not good enough. I don’t want to load the whole library just to use a few methods, I also don’t want to manually check what is being used and sometimes we only use a small subset of a “module”.

I know it is hard to split everything and that there are a lot of cross-dependencies on the internal code, but it just doesn’t feel right. On most projects I probably use less than 20 methods – filter, closest, toggleClass, remove and $ gets 70% of the job done.

I know I could use Google Closure Compiler Advanced Mode to remove the unused code branches but in some cases that isn’t possible. In the past couple years I worked on projects that required a substantial amount of JavaScript (20-50K LOC) and lazy loading some parts of the app was essential for the performance, creating an externs file can be tricky and not really easy to maintain in the long run, besides the fact that I use AMD modules on almost all projects which makes it even harder.

I would like to see complete modularity, being able to require a single method and use it as a standalone function:

var $ = require('jquery/$');
var foo = $('.foo');
// loading a new method would augment the `jQuery.prototype`
var find = require('jquery/dom/find');
foo.find('.bar');
// but also work as a standalone function
find(foo, '.bar');

That way it is clear that my module depends on methods XYZ and my build will take care of bundling only what is being used. You can also use a tool like node-madge to get some cool dependency graphs and help during refactoring.

By requiring individual methods you discourage the use of too many dependencies, which also have the added bonus of smaller source files and modules that take care of a single responsibility. I’ve been doing that for the past year with amd-utils and it’s working great.

Of course there should be a way to require the whole package at once for brevity:

// load whole jQuery package
require('jquery');
// or individual modules
require('jquery/dom');
require('jquery/css');
require('jquery/ajax');

Lazy users could still be lazy but would allow the freaks to control what they load and how they use it.

The list goes on…

I didn’t listed all the things, just the ones that bothers me more, but it should be enough to show that there are a lot of things to be improved and that lack of consistency is my main complaint. If you are a jQuery maintainer please consider a few of them for the future.

VanillaJS & Why I use jQuery

Yes, for modern browsers jQuery isn’t so relevant but it is still nice to have better abstractions, it shields the app from browser discrepancies and increases productivity. Nicholas Zakas gets into more details on The Problem with Native JavaScript APIs, worth a read.

jQuery is my library of choice for most projects (even if working by myself) mainly because it is popular. It’s easier to find someone to maintain/contribute, there are a lot of opensource plugins and components, bugs are spotted/fixed quickly since it’s being used by millions of sites, etc. It is also very productive after you get used to it…

I guess sometimes “worse” is really “better”.

Further Reading


Comments

Great blog post! My coworker was just complaining to me that the $.inArray method should be called indexOf.

Great post. I'm wondering if someone (not me!) could just fork it and go ahead with that. Renaming methods, removing overloads, should be straight forward. The modules part, maybe not.

Great links, also. Now looking into Madge and Signals.

Jean Carlo Emer

Hi, I and a friend are working in a library. We are trying to resolve some of this goals.

Checks the code and API https://github.com/jcemer/rye

Great summary of the glaring issues in jQuery. For a personal project of mine I thought I would extract the cross-browser-consistency specific logic from the jQuery-isms logic so that I could better understand all of the browser quirks jQuery deals with as well as the internals of the jQuery project as a whole. I have to say, the overloading of methods in jQuery, like $(), has left them in a pretty tightly coupled corner. Navigating the logic branches for various overloads is often painful and can even be dispersed throughout the codebase with their 'Hooks' architecture.

@Andre Torgal - Unfortunately most of the core and many of the performance optimizations baked into jQuery rely on the quirky code structure. Forking the project to clean it up would be quite the effort, and in the end you'd be left with 10k lines of code that nobody would use because they're used to jQuery and its amazing support network. Try to extract the DOMContentLoaded logic or the data()/attr() methods as a good test of how challenging it is to mentally parse the code.

Here's an interesting post on the subject: http://ariya.ofilabs.com/2012/12/complexity-analysis-of-javascript-code.html Looks dead right now, but the article is available on the homepage: http://ariya.ofilabs.com/

and here's a graphic: http://ariya.ofilabs.com/2012/12/complexity-analysis-of-javascript-code.html/complexity

2.0 should have been a rewrite.

JQuery is mostly nice but yeah, some things are pretty bad; I also use a small subset, mostly DOM selection and basic manipulation.

The effects are a bit of a joke; Effects on different elements don't run in sync, the most basic easing strategies are not available without using a plugin, etc.

XHR is bloated; Sometimes you refactor your code slightly and you start getting runtimes because JQuery decided to pass a different argument to your result callback! (Sometimes get an ajaxArgs, an Array with 3 elements, sometimes the already parsed response, etc) Just stop trying to be too smart, even dynamic languages have their limit.

What flips my shit about $.each, etc is that jQuery isn't even consistent - $.each() and .each() take different argument orders! And $.grep() is like .each(), not like $.each(). WTF, jQuery.

Leave a Comment

Please post only comments that will add value to the content of this page. Please read the about page to understand the objective of this blog and the way I think about it. Thanks.

Comments are parsed as Markdown and you can use basic HTML tags (a, blockquote, cite, code, del, em, strong) but some code may be striped if not escaped (specially PHP and HTML tags that aren't on the list). Line and paragraph breaks automatic. Code blocks should be indented with 4 spaces.