4 ms·
The way I see it once you’ve thinned the polyfills to next to nothing, the enduring feature of jQuery is the automatic list comprehensions. The ability to unsel
by hinkley 2y ago
The way I see it once you’ve thinned the polyfills to next to nothing, the enduring feature of jQuery is the automatic list comprehensions. The ability to unselect all of the buttons in a form in a single call is still hard to match elsewhere. That and parent queries.
The main problem I have with the implementation is that it chooses to fail silently when the list is empty. I’ve fixed too many bugs of this sort, often caused by someone refactoring a DOM tree to do some fancy layout trick after the fact. If I were implementing jquery again today, I’d make it error on empty set by default and add a call chain or flag to fail silently when you really don’t care. I’ve spent a few hours poking around at jQuery seeing what it would take to pull out sizzle and do this, but never took things any farther than that.
At the end of the day jquery is about the old debate of libraries versus frameworks. We’ve been doing SPAs with giant frameworks long enough now for the Trough of Disillusionment to be just around the corner again.
- luckylion 2y agoNot having to worry whether some selector matches any elements is part of what makes jQuery attractive to many though. It's very "fire and forget", you send off your command to hide all .foo, and if there are any .foo they will be hidden, and if there are no .foo, nothing happens and you don't need to worry about it, much like CSS. If you write .foo { color: red; } and there isn't any .foo in the document it doesn't do anything but also has no negative side-effects (except that tiny overhead).
- bussyfumes 2y agoJust had a script today that fires in 2 contexts and ran into an error where the element I attach a handler to doesn't exist in one of the contexts which breaks JS on the page. Since I already had jQuery as a dependency in the project, in the moment it felt easier to replace the querySelector call with jQuery, which I did, instead of checking the querySelect result so I second this, the 'fire and forget' part still holds up very well even though the tree traversal pain points have mostly been solved by browsers.
- wwweston 2y agoExactly this. It's extremely useful to be able to say "with all document elements that matches this selector (whether there are any or not), do this." It's possible it's also situationally useful to say "if there aren't any document elements that match this selector, error out, if so do this with all of them." I'm struggling to imagine a specific situation in which that has compelling advantages (and would be interested in elaboration), but let's say it exists. Then something like this: $.fn.mustMatch = function(onNoMatch) { if (!this.length) { if(typeof onNoMatch == 'function') onNoMatch(this); else throw "mustMatch failed on selector " + this.selector; } return this; } would make it easy to explicitly add the guard condition with an invocation like `$("#selector .nonextant").mustMatch().each(function (emt) { /*do this*/ })`, rather than having it invisibly ride along with every comprehension and making the "whether there are any or not" case harder. And if for some reason one were possessed of the conviction that implicit enforcement of this universally within their project outweighed the advantages of explicit options for both ways, it'd probably be better to patch the jQuery lib for that specific project than enforce it as a standard for everyone worldwide.
- moritzwarhier 2y agoOptional chaining is widely supported and solves this problem for single-element queries. qsA returns an empty NodeList when the selector matches no elements, as long as the string is a valid selector. Then forEach doesn't require it anyway.
- almd 2y agotrue it is tedious but I have a vs code shortcut for doing the following (and same goes for queryselectorall) let foo = document.queryselector(‘.foo’); if (!!foo) { //do thing }
- hinkley 2y agoFor every one of these we had ten where a button press definitely needed to definitely update a DOM element. As I said above, you’d want a way to override the behavior in the few cases where it’s inappropriate
- spankalee 2y agoYou can modify every item in a query pretty nicely with a one-liner in modern browsers now: document.querySelectorAll('input[type=checkbox]').forEach((i) => i.checked = false); This takes advantage of iterable NodeList and iterator helpers. Many parent queries can be done with element.closest()
- tored 2y agoFully agree, the default should be strict, jQuery based code requires every developer to be aware of every selector used in the project and remember to update it when the DOM changes. That is of course impossible. I think it is possible to replace the jQuery init function with your own implementation that enforces length.
- tored 2y agoEnjoy! <html> <head> <script src="https://code.jquery.com/jquery-4.0.0-beta.2.js"></script> </head> <body> <ul id="list"> <li>foo</li> <li>bar</li> <li>gnord</li> </ul> <script> (function ($) { "use strict"; if ("development") { // jQuery strict mode, logs errors on empty selectors if not opting out const getStackTrace = function (error) { const stack = error.stack || ''; return stack .split('\n') .map(function (line) { return line.trim(); }) .filter(function (line) { return !!line; }); }; // by calling try() we silence selectors that returns empty // problem is that this function runs after the selector $.fn.try = function () { this.__store.try = true; return this; }; // thus we use the GC to check when the jQuery object is destroyed const registry = new FinalizationRegistry((store) => { if (!store.try) { console.error( 'Empty result for selector "' + store.selector + '"', getStackTrace(store.error) ); } }); // override the init method const jQueryInit = $.fn.init; $.fn.init = function (selector, context) { const result = new jQueryInit(selector, context); if (selector && result.length === 0) { const store = {selector: selector, try: false, error: new Error()}; result.__store = store; registry.register(result, store); } return result; }; } else { $.fn.try = function () { return this; }; } })(jQuery); // normal usage, have result, no error $("#list li").each(function (i, el) { console.log(el); }); // empty result, triggers error log $("#nolist li").each(function (i, el) { console.log(el); }); // empty result but with try, no error $("#trylist li").try().each(function (i, el) { console.log(el); }); </script> </body> </html>