- Cash ist eine sehr kleine jQuery-Alternative, die eine jQuery-ähnliche Syntax zur DOM-Manipulation in modernen Browsern ab IE11+ bietet
- Durch Nutzung moderner Browserfunktionen reduziert sie die Codebasis und macht vertraute chaining-fähige Methoden mit deutlich kleinerer Dateigröße verfügbar
- Sie zielt nicht auf 100%ige Funktionsgleichheit mit jQuery ab, deckt aber die meisten alltäglichen Use Cases ab; die implementierten APIs sind weitgehend mit jQuery kompatibel
- Die Größe beträgt minified & gzipped 6 KB und ist damit 76,6 % kleiner als jQuery Slim 3.4.1 mit 24,4 KB; mit Partial Builds lässt sie sich weiter reduzieren
- Unterstützt werden eine TypeScript-Codebasis, aus dem Code generierte TypeScript-Typen, namespaced Events sowie Partial Builds, bei denen einzelne Methoden ausgeschlossen werden können
Welches Problem Cash löst
- Cash ist eine jQuery-Alternative für moderne Browser und bietet für die DOM-Manipulation den jQuery-ähnlichen
$()-Selektor sowie chaining-fähige Collection-Methoden
- Unterstützt werden Browser ab IE11+
- Ziel ist nicht, sämtliche jQuery-Funktionen identisch nachzubilden; die von Cash implementierten Funktionen sind jedoch größtenteils auf Kompatibilität mit der jQuery-API ausgelegt
- Nutzer, die von jQuery migrieren, können den migration guide lesen
Vergleich von Größe und Funktionen
- Beim Vergleich der Dateigröße ist Cash kleiner als Zepto 1.2.0 und jQuery Slim 3.4.1
- Unminified: 36,5 KB
- Minified: 16 KB
- Minified & Gzipped: 6 KB
- jQuery Slim 3.4.1 kommt minified & gzipped auf 24,4 KB; Cash reduziert die Größe im Vergleich dazu um 76,6 %
- Wenn ein noch kleineres Bundle benötigt wird, können partial builds verwendet werden
- Im Funktionsvergleich bietet Cash Unterstützung für moderne Browser, aktive Wartung, namespaced Events, eine TypeScript-Codebasis und aus dem Code generierte TypeScript-Typen
- Bei Partial Builds können in Cash einzelne Methoden ausgeschlossen werden, während Zepto und jQuery Slim als Ausschluss ganzer Module ausgewiesen sind
Verwendung
- Cash kann über jsDelivr geladen und direkt im Browser verwendet werden
<script src="https://cdn.jsdelivr.net/npm/cash-dom/…;
<script>
$(function () {
$('html').addClass ( 'dom-loaded' );
$('<footer>Appended with Cash</footer>').appendTo ( document.body );
});
</script>
- Das npm-Paket ist unter dem Namen
cash-dom verfügbar
npm install --save cash-dom
import $ from "cash-dom";
$(function () {
$('html').addClass ( 'dom-loaded' );
$('<footer>Appended with Cash</footer>').appendTo ( document.body );
});
API-Struktur
$() ist die zentrale Selektormethode von Cash und gibt eine manipulierbare Collection von Nodes zurück
- Wird eine Funktion übergeben, wird diese ausgeführt, sobald das DOM bereit ist
$() kann einen Selektor, einen DOM-Node, eine NodeList, einen HTML-String, eine Cash-Collection oder einen Document-Ready-Callback entgegennehmen
- Cash bietet im Wesentlichen drei API-Bereiche
- Query-Selektoren
- Collection-Methoden
- Library-Methoden des globalen
$-Objekts
Collection-Methoden
- Collection-Methoden werden aufgerufen, nachdem mit
$() eine Collection erstellt wurde, etwa $(element).addClass(className)
- Die angebotenen Kategorien gliedern sich in Attribute, Collections, CSS, Daten, Dimensionen, Effekte, Events, Formulare, DOM-Manipulation, Offsets und Traversierung
- Zu den wichtigsten Methoden gehören
- Klassen/Attribute:
addClass, removeClass, toggleClass, attr, prop, removeAttr
- Collection-Verarbeitung:
add, each, eq, filter, first, get, map, slice
- DOM-Manipulation:
append, prepend, before, after, html, text, clone, remove, replaceWith, wrap
- Events:
on, off, one, ready, trigger
- Traversierung:
find, children, closest, parent, parents, siblings, next, prev
- Einige extra methods werden bereitgestellt, sind aber standardmäßig deaktiviert
$.fn ist das Haupt-Prototypobjekt der Collection; darüber lassen sich wie bei Plugins eigene Methoden zu allen Collections hinzufügen
Globale Cash-Methoden
- Das globale
$-Objekt enthält Typprüfungs- und Utility-Methoden
- Die Typprüfungsmethoden umfassen
$.isArray, $.isFunction, $.isNumeric, $.isPlainObject und $.isWindow
- Zu den Utilities gehören
$.guid, $.each, $.extend, $.parseHTML und $.unique
$.extend erweitert ein Zielobjekt um die Eigenschaften von Quellobjekten und unterstützt auch Deep Extend
$.parseHTML gibt aus einem HTML-String eine Collection zurück, und $.unique gibt ein neues Array ohne Duplikate zurück
Erweiterung und Beiträge
- Cash kann mit eigenen Methoden erweitert werden; die Vorgehensweise ist unter extending Cash beschrieben
- Probleme oder Feature Requests können als GitHub-Issue eröffnet werden
- Der Pull-Request-Workflow besteht aus Repository klonen, Abhängigkeiten installieren, automatische Neukompilierung mit
npm run dev, Tests mit npm run test ausführen und bei Bedarf das README aktualisieren
- Die Lizenz ist MIT
1 Kommentare
Hacker-News-Meinungen
Da Browser heutzutage besser geworden sind, reichen zur Vereinfachung von DOM-Manipulationen oft schon die folgenden zweizeiligen Aliasse
dqs = document.querySelector.bind(document);dqsA = document.querySelectorAll.bind(document);So kann man statt
document.querySelector('#country')einfachdqs('#country')verwenden und stattdocument.querySelectorAll('.city')dqsA('.city')Für den Rest kann man problemlos einfach native Browser-Funktionen nutzen; meistens importiert man sie aus einem Modul, etwa mit
import { dqs, dqsA } from '/lib/js/dqs.js';https://github.com/no-gravity/dqs.js
querySelector/querySelectorAllgebunden werden, wirken nützlich und vernünftig, aber sie perimporthereinzuholen ist übertriebenEs sind nur zwei einfache Zeilen; es gibt keinen Grund, daraus eine Abhängigkeit zu machen — man kann sie einfach kopieren und einfügen
Zum Beispiel sind
$.fn.one()oder$.fn.on()für mehrere Events mit jQuery/Cash einfacher zu verwenden, und wenn man sich die interne Implementierung ansieht, steckt ziemlich viel Arbeit darin: https://github.com/fabiospampinato/cash/blob/master/src/even...querySelectorAll()liefert keine Live-Collection, daher wird es oft direkt so in ein Array umgewandeltdqsA = s => Array.from(document.querySelectorAll(s));Dadurch kann man auf dem Ergebnis sofort Array-Methoden wie
.map()oder.filter()verwenden, wodurch es sich ähnlich wie jQuery anfühltZum Beispiel wurde in Safari während der Tests das
select-Event eines bestimmten Elements überhaupt nicht ausgelöst, und in manchen Browsern gab es kein Event, wenn nur der Caret bewegt wurdeSelbst wenn man React-Funktionen wie Komponenten, State oder Props nicht braucht, reichen native Browser-Funktionen nicht unbedingt aus; der Wert von React DOM, Browser-Unterschiede zu überdecken, wurde deutlich
Wenn man fast alle Polyfills entfernt hat, ist der verbleibende dauerhafte Vorteil von jQuery aus meiner Sicht die automatische Listenverarbeitung
Die Fähigkeit, die Auswahl aller Buttons in einem Formular mit einem einzigen Aufruf aufzuheben, ist anderswo immer noch schwer zu erreichen, und bei Parent-Queries ist es ähnlich
Das größte Implementierungsproblem ist allerdings, dass es still fehlschlägt, wenn die Liste leer ist
Weil ich schon zu viele solcher Bugs behoben habe, die entstanden sind, nachdem der DOM-Baum später aus Layout-Gründen refaktoriert wurde, würde ich, wenn ich jQuery heute neu bauen würde, standardmäßig bei einer leeren Menge einen Fehler ausgeben und nur dann per Chain-Aufruf oder Flag still fehlschlagen, wenn es wirklich egal ist
Früher habe ich ein paar Stunden damit verbracht zu prüfen, ob man Sizzle herauslösen und es in diese Richtung ändern könnte, bin aber nicht weitergegangen
Letztlich hängt jQuery auch mit der alten Debatte Library versus Framework zusammen, und nach langer Arbeit mit Single-Page-Apps in riesigen Frameworks fühlt es sich so an, als käme das Tal der Enttäuschung wieder näher
Wenn man anweist, alle
.foozu verstecken, werden sie versteckt, falls es sie gibt, und wenn nicht, passiert nichts — ein Fire-and-forget-Ansatz, ähnlich wie bei CSSWenn man
.foo { color: red; }schreibt und es im Dokument kein.foogibt, hat das außer einem kleinen Overhead keine Nebenwirkungendocument.querySelectorAll('input[type=checkbox]').forEach((i) => i.checked = false);Das nutzt eine iterierbare
NodeListund Iterator-HilfsfunktionenViele Parent-Queries lassen sich mit
element.closest()erledigenBei jQuery-basiertem Code müsste jeder Entwickler alle Selektoren des Projekts kennen und sie aktualisieren, wenn sich das DOM ändert — was natürlich unmöglich ist
Es scheint möglich, die jQuery-Initialisierungsfunktion durch eine eigene Implementierung zu ersetzen und so eine Längenprüfung zu erzwingen
In einer Situation, in der Mainstream-Websites buchstäblich Megabytes an JavaScript ausliefern, verstehe ich nicht, warum man eine ganze Bibliothek mit weniger Funktionen neu schreiben sollte, nur um 50 KB zu sparen
Viele Artikel sprechen sich wegen Paketgröße und Bandbreitenbeschränkungen gegen jQuery aus und verteidigen gleichzeitig SPA-Frameworks, die viel mehr Bandbreite verbrauchen
Das ist völlig unsinniges Cargo-Cult-Denken
In allen drei Fällen ist die Antwort dieselbe: Das Große bin nicht ich, und ich mache etwas anderes, Kleineres
Eine weitere Antwort ist: Wenn etwas zu groß ist, klingt kleiner werden nach einer Lösung
Letztlich scheint die eigentliche Frage zu sein, warum Entwickler Zeit damit verbringen, eine Bibliothek neu zu schreiben, aber so überraschend ist das nicht
Ein erheblicher Teil des Programmierens besteht darin, bereits Vorhandenes neu zu schreiben — weil es beruflich nötig ist, weil man ein etwas anderes Verhalten oder andere Performance-Eigenschaften braucht oder weil man einfach lernen möchte, wie es funktioniert
Wenn du nach einer jQuery-Alternative suchst: Ich habe zu lange auf jQuery 4.0 gewartet und am Ende etwas Eigenes gebaut, das jQuery ähnelt, aber ein paar wichtige Unterschiede hat.
Animationen, Tweens und Timelines nutzen reines CSS statt des Custom-Systems von jQuery, behandeln einzelne Elemente und Listen transparent und sind eher auf Inline-Nutzung ausgerichtet.
Bei
me()steht, dass es ein einzelnes Element zurückgibt, entweder das erste Element odernull, und beiany()steht, dass es ein Array oder ein leeres Array zurückgibt.Die Beispiele darunter wie
any('button')?.forEach(...)undany('button')?.map(...)deuten aber an, dass auchnullmöglich sein könnte.Ich bin verwirrt, ob
any()wie oben beschrieben immer ein Array zurückgibt oder ob wie in den Beispielen unten auchnullmöglich ist.Besonders interessiert mich Lokalität des Verhaltens.
Mich würde interessieren, welche Erfahrungen es mit
currentScript.parentElementgibt.Als ich letzten Monat kurz recherchiert habe, hatte ich den Eindruck, dass es in Randfällen vermutlich nicht zuverlässig ist, aber ich weiß nicht mehr genau, wann das war.
Ich bin nicht tief eingestiegen, und es freut mich, dass es zum Laufen gebracht wurde.
Unter der Annahme, dass es nicht
asyncodermoduleist, müsstecurrentScript.parentElementdoch auch dann in allen Browsern weiterhin funktionieren, wenn drei aufeinanderfolgende Skripte geladen werden, oder?SvelteKit hatte diese Diskussion ebenfalls und implementierte am Ende zufällige IDs, um das Zielelement anzugeben: https://github.com/sveltejs/kit/issues/2221
Beim Lesen des Migration Guides habe ich ein paar Dinge gelernt, die jQuery kann, Cash aber nicht, die ich nicht kannte und irgendwann einmal ausprobieren könnte.
https://github.com/fabiospampinato/cash/blob/master/docs/mig...
Als Erweiterungsziel wäre es schön, mit TypeScript-Template-String-Magie den Elementtyp exakt abzuleiten.
Zum Beispiel könnte statisch erkannt werden, dass
$('div#name')einHTMLDivElementist.typed-query-selector.Ein echtes Nutzungsbeispiel gibt es hier: https://github.com/GoogleChrome/lighthouse/blob/main/types/i...
Ich weiß nicht, ob das in TypeScript möglich ist, und sehe auch nicht richtig, wie es gehen könnte.
Ich habe gehört, jQuery 4 sei die jQuery-Alternative für moderne Browser.
In einer Browser-Erweiterung, an der ich arbeite, habe ich das anfangs verwendet, bin aber am Ende zu einer JSX-Bibliothek gewechselt.
Sobald man über den Bereich „einfache App“ hinauskommt, wird jQuery schnell zu Code, der schwer nachzuvollziehen ist; das empfinde ich auch als jemand, der selbst eine von jQuery inspirierte Bibliothek gebaut hat.
Am Ende muss man das passende Werkzeug für die Aufgabe verwenden.
[1]: https://github.com/aleclarson/dough
Wenn man jQuery in mittelgroßen bis großen Apps gut beherrschen kann, ist das in Ordnung, aber es ist nicht mein Geschmack.
Früher, als ich JS reduzieren wollte, habe ich https://github.com/filamentgroup/shoestring verwendet.
Der Hauptgrund war, dass es Custom Builds bot, in die man nur das packt, was man wirklich braucht.
Cash scheint etwas Ähnliches zu haben, aber es ist in der Dokumentation etwas versteckter: https://github.com/fabiospampinato/cash/blob/master/docs/par...
Wenn ich es verwenden würde, würde ich das wohl zuerst ausprobieren.
Trotzdem denke ich immer noch, dass es heutzutage die bessere Wahl ist, direkt das zu nutzen, was der Browser bereitstellt.
Das ist tatsächlich ziemlich gut, und jQuery ist nicht mehr unbedingt nötig.
Besonders wenn man bedenkt, dass selbst eine kleine jQuery-Alternative 6 kB groß ist, während Preact, eine React-ähnliche Bibliothek, nur halb so groß ist.
Ich bin mir nicht sicher, ob es viel mehr hilft, als bestehenden Web-APIs Aliasse zu geben.