Warum bombardiert jQuery nicht, wenn Ihr Auswahlobjekt ungültig ist?

73

Ich habe kürzlich Code in der Art von verwendet

$("#divMenuContainer:visible").hide("explode");

Nachdem ich einige Zeit damit verbracht hatte, es zum Laufen zu bringen, stellte ich fest, dass mein Selektor auf ein nicht vorhandenes Div verwies.

Das Ergebnis der Abfrage war einfach, dass sie nicht ausgeführt wurde.

Offensichtlich ist dies beabsichtigt. Kann jemand die Logik erklären, warum diese Designentscheidung getroffen wurde, anstatt eine Ausnahme zu machen?

Nicht versuchen zu kritisieren, sondern nur zu verstehen.

Maxim Gershkovich
quelle
12
Einverstanden, ich liebe Fragen wie diese. "Das passiert, wenn ich das mache ... warum?" ist wesentlich interessanter als "Es ist kaputt, repariere es!"
David
Wenn Sie dies auf der JQuery-Mailingliste fragen, erhalten Sie wahrscheinlich fundiertere Antworten. Es sei denn, Jeresig ist hier :)
Jrharshath
7
@Here - Er ist: stackoverflow.com/users/6524/john-resig
Nick Craver
1
Ich bin mir nicht sicher warum , aber ich bin sicher froh, dass es so ist!
Josh Stodola
3
Genau deshalb habe ich dieses kleine Drehbuch geschrieben: github.com/mikeycgto/jquery.whiny.js
mikeycgto

Antworten:

53

Hier gibt es einige gute Gründe: "Verkettbarkeit" ist der Hauptantrieb. Die Fähigkeit, sehr knappen Code durch Verketten zu schreiben, muss keine Fehler verursachen, um scheinbar reibungslos zu funktionieren, zum Beispiel:

$("#divMenuContainer:visible").hide("explode").add("#another").fadeIn();

Jedes Objekt in der Kette, auch wenn es auf keine DOM-Elemente verweist, wurde möglicherweise später hinzugefügt, oder nehmen wir ein anderes Beispiel:

$("#divMenuContainer:visible").live("click", function() { ... });

In diesem Fall kümmern wir uns nicht um die Elemente, die der Selektor gefunden hat, sondern um den Selektor selbst. Hier ist ein anderes:

$("#divMenuContainer:visible").find(".child").hide("explode").end().fadeOut();

Selbst wenn es keine Kinder gibt, möchten wir möglicherweise später wieder in die Kette springen und weiterhin die .prevObjectReferenz verwenden, um die Kette wieder hochzufahren.

Es gibt Dutzende solcher Fälle, die zeigen, dass die Bibliothek so ist, wie sie ist. Wie für die warum , aus Interviews von John Resig , der der Schöpfer von jQuery ist, stellt er fest, dass das nur , wie es funktioniert. Er war nach Code so knapp wie möglich, und das Verkettungsmodell ist das, was aus dem Hut kam. Es hat zufällig auch viele Vorteile, das obige Beispiel sind nur einige davon.

Um es klar auszudrücken , ich sage nicht, dass jedes Attribut der Verkettung gut ist, es gibt nur viele Vorteile.


Nehmen wir diese Seite als Beispiel. Was wäre, wenn wir so etwas hätten:

$(".comment").click(replyToFunction);

Sollte das fehlschlagen, weil es noch keine Kommentare gibt? Nun, nein, nicht wirklich, das wird erwartet, ich würde hier keinen Fehler wollen ... wenn das Element existiert, mach es, wenn nicht. Mein Punkt ist, zumindest meiner Erfahrung nach, dass es enorm nützlicher ist , keinen Fehler wegen eines fehlenden Elements zu werfen, als einen zu werfen.

Der Selektor in Ihrer Frage, der #IDSelektor, ist ein ganz besonderer Fall, in dem Sie nur ein einziges Element erwarten. Vielleicht könnten Sie also argumentieren, dass es dort fehlschlagen sollte ... aber dann wäre das nicht mit anderen Selektoren vereinbar, und Sie möchten eine Bibliothek konsequent sein.

Bei so ziemlich jedem anderen Selektor erwarten Sie 0-viele Elemente. Wenn Sie also keine Elemente finden, ist ein Fehlschlagen in den meisten Situationen erheblich weniger wünschenswert, insbesondere in den .live()oben genannten Fällen .

Nick Craver
quelle
Die Kehrseite dieser Münze ist, dass Sie, wenn Sie etwas tun möchten, wenn kein Element vorhanden ist, viele Überprüfungen auf leere Auswahlergebnisse durchführen müssen. Das will man natürlich nicht. Wenn ein Element auf einer Seite fehlt, obwohl dies nicht der Fall sein sollte, ist die Ausgabe Ihres Backends falsch. Dies kann durch Testen der Ausgabe unter bestimmten Bedingungen abgefangen werden.
Cthulhu
9
@Cthulhu - Wenn Sie überprüfen möchten, if(!$("selector").length) { }ist dies ein ziemlich kurzer Weg :) Sie können auch eine statische Methode für so etwas definieren.
Nick Craver
Es kann nützlich sein, das Nullobjektmuster zu erwähnen.
Matt Ball
@Bears - Für einige Beispiele passt es zu diesem Muster, für andere, als ob .live()wir uns nicht einmal um die darin enthaltenen DOM-Elemente kümmern ... es ist nicht so, dass die Liste null oder leer ist, wir kümmern uns nur nicht darum. Einige Dinge mit Verkettung verwenden es, andere nicht, manchmal verwenden sie andere Eigenschaften ( .selectorund .contextin diesem Fall .prevObjectin anderen).
Nick Craver
3
@sfink - Sicher, wenn Sie das wollten, würde es so aussehen: jQuery.fn.require = ​function(num, num2) { if(this.length < num) $.error("At least " + num + " elements are required"); if(num2 && this.length > num2) $.error("Between " + num + " and " + num2 + " elements required"); };Sie können es hier testen / spielen: jsfiddle.net/nick_craver/VrXmc Überprüfen Sie die Konsole auf Fehler.
Nick Craver
56

Betrachten Sie es als eine Abfrage , die es ist. Sie fragen nach allen "Datensätzen" (DOM-Elementen), die Ihren Kriterien entsprechen. Das Ergebnis ist ein Satz von Nulldatensätzen.

Anschließend werden Ihre Null-Datensätze durchlaufen und die Aktion auf sie angewendet. :) :)

Wenn Sie dasselbe mit SQL oder einem Array tun, verhält es sich in den meisten Sprachen genauso. Eine Sammlung von Nulldatensätzen ist kein Fehlerzustand.

var things = $("invalid selector");
$("p").text("The object is valid: " + things + " but has " + things.length + " elements.")
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<p></p>

Matt Sherman
quelle
6
Das Fehlen eines Objekts ist ebenso informativ wie seine Anwesenheit;) Sie müssen nur dafür entwerfen.
dmp
Was Sie über das Durchlaufen einer Reihe von Datensätzen sagen, ist für Tags und Klassen sinnvoll, nicht jedoch für IDs, bei denen nur eine einzige Instanz vorhanden sein sollte. Ich möchte nur darauf hinweisen.
Gerry
8

Es ist eine Frage der Flexibilität. Ich selbst möchte den gleichen Schutz, den Sie verlangen, Sie können es immer selbst tun. Verwenden:

jQuery.fn.single = function() {
    if (this.length != 1) {
        throw new Error("Expected 1 matching element, found " + this.length);
    }

    return this;
};

und verwenden Sie jetzt $ ("input: checked"). single () mit der Gewissheit, dass entweder ein einzelnes Element zurückgegeben wird oder Sie einen Fehler erhalten.

Frank Schwieterman
quelle
6

jQuery () gibt immer ein jQuery-Objekt zurück, um Fehler zu vermeiden, aber was noch wichtiger ist:

So können Sie reaktionsschnellen Code schreiben.

do X if Y is present

Wenn Y nicht vorhanden ist, berechnet X nicht.

Dies bedeutet, dass Sie globale Init-Cross-Pages haben können und nur Init-Plugins, ob sie etwas finden oder nicht.

$(function(){
    // does nothing if the page contains no element with className 'accordion'
    $('.accordion').implementAccordion();
    // usually on a single page, but we can add it to a global js file nontheless.
    $('.validate-form').implementFormValidator();
});

Natürlich sind einige Plugins wirklich schlecht geschrieben und werfen einen Fehler auf.

BGerrissen
quelle
5

Es ist Teil der Philosophie von jQuerys (eigentlich John Resigs, denke ich) über die Bibliothek.

Es versucht, "freundlich" zu Ihnen und Ihren Kunden zu sein, was wiederum bedeutet, dass es ziemlich selten Ausnahmen gibt
(wie wirklich selten).

Aber wie immer können Sie es einfach erweitern wie:

(function(_jQuery){
    jQuery = function(){
        var ret = _jQuery.apply(this, arguments);
        if(!ret.length) throw new Error('empty selector');
        return ret;
    };
}(jQuery));

Aber wie Nickin einem Kommentar gesagt, ist dies meistens kein gewünschtes Verhalten. Wenn Sie es trotzdem aus irgendeinem Grund haben möchten, sollte es ein Code-Snippet wie das oben genannte tun.

jAndy
quelle
2
Bei einer solchen Entwicklungsphilosophie sollte es unbedingt zwei Modi geben, einen, der nachsichtig ist (Produktion) und einen, der früh und häufig auslöst (zum Debuggen). Wie kann eine vernünftige Person sonst ein Debugging durchführen? Ich kenne jQuery überhaupt nicht - vielleicht gibt es einen solchen Modus?
Konrad Rudolph
@Konrad: Fügen Sie einfach den obigen Code in eine Datei 'debug.js' ein und fügen Sie ihn nur während der Entwicklung in Ihre Quelle ein. Diese Art von Dingen ist ein weit verbreitetes Muster.
Rfunduk
2
@Konrad: Ein unübertroffener Selektor ist an und für sich kein Fehler. Es ist nicht wie bei HTML, wo es einen (einigermaßen) strengen Standard und milde Browser gibt, die die Einhaltung nicht erzwingen - leere Sätze sind in vielen (wenn nicht den meisten) Programmen ein nützliches und verlässliches Verhalten. Wenn Ihr Programm nicht funktioniert, wenn ein Selektor nicht übereinstimmt, müssen Sie überprüfen, ob dies der Fall ist.
Shog9
@ Shog9: Das ist nicht das Problem. Das Problem ist: Warum zum Teufel funktioniert das nicht ??? ... x Zeit später: Oh Scheiße, ich hatte einen Tippfehler mit einem Zeichen und mein Selektor passte eigentlich zu nichts; oder ich habe versehentlich eine Klasse anstelle einer ID in meinem HTML festgelegt und die ganze Zeit habe ich versucht, eine ID zu finden.
Gerry
3

Ein Selektor, der sich nicht auf Elemente bezieht, ist immer noch ein legaler Selektor und kann beabsichtigt sein. Es kann sein, dass ein bestimmter Selektor manchmal Elemente zurückgibt, und Sie möchten einen solchen Selektor verwenden können, ohne die Möglichkeit zu haben, Laufzeitfehler auszulösen.

rekursiv
quelle
3

Ein gutes Beispiel ist, wenn Sie mit allen aktivierten Kontrollkästchen etwas unternehmen möchten

$("input:checked")

... Sie wissen nicht, wie viele überprüft werden. Könnte jeder sein, alle oder keine. Das hängt vom Benutzer ab.

Also, anstatt Code wie schreiben zu müssen

var checkedInputs = $("input:checked");
if (checkedInputs  && checkedInputs .length > 0) {
      checkedInputs .doStuff();
}

Sie können einfach

$("input:checked").doStuff();

Und wenn sie eine Auswahl getroffen haben, großartig, wird alles erledigt. Wenn nicht ... kein Schaden, kein Foul.

CaffGeek
quelle
1
Zu Ihrer Information - Der erste Test in Ihrer if()Erklärung wird immer bewertet true, sodass Sie einfach Folgendes tun können : if(checkedInputs.length > 0) {.... Oder da eine beliebige Zahl größer als 0gleich ist true, können Sie sie auf reduzieren if(checkedInputs.length) {.... : o)
user113716
2

da $("#thisdivdoesntexist")in jQuery immer noch eine 'leere' jQuery zurückgegeben wird Objectund alle jQuery-Objekte ihre Methoden haben, also kein Fehler.

Das ist eigentlich eine gute Sache. Wenn dies einen Fehler auslösen würde, müssten Sie in vielen Fällen einige zusätzliche Überprüfungen durchführen, bevor Sie etwas tun, was bedeutet, dass Sie viel Überlastungscode haben würden. Obwohl es keine schlechte Praxis wäre, vor dem Aufrufen einer Methode für das Objekt zu überprüfen, ob sie vorhanden ist, wird nicht alles Javascript angehalten, wenn der Selektor nichts zurückgibt (was passieren würde, wenn es einen Fehler auslösen würde).

Daher können Sie Selektoren global verwenden, auch wenn einige Seiten nicht über den Selektor verfügen, ohne sich darüber wirklich Gedanken machen zu müssen.

Stefanvds
quelle
Ja, gibt $('something')immer ein jQuery-Objekt zurück. Im Fall von $("#thisdivdoesntexist")hat das Objekt 0 Elemente.
Rocket Hazmat
Auf jeden Fall ist dies natürlich ein Aufruf an jQuerys constructor function, aber dies sollte wahrscheinlich zumindest eine Warnung auslösen .
Andy
Wie würden Sie eine Warnung werfen?
Stefanvds
1

Normalerweise fahre ich nur fort, wenn die Elemente existieren, indem ich etwas in der Art von:

var aThing = $("#myElement");
if(aThing.length){
    //my code here
}
CBarr
quelle
0

Ich denke, dass es wahrscheinlich damit zu tun hat, dass Ihr Selektor:

$("#divMenuContainer:visible")

Unter dem Deckblatt wird ein jQuery-Objekt zurückgegeben, das eine Reihe möglicher Übereinstimmungen enthält. Die Funktion Ausblenden wird dann für jede dieser Funktionen ausgeführt. Ich stelle mir vor, dass es in diesem Fall keinen Sinn macht, keine Ausnahme auszulösen, da Sie eine Liste mit null Einträgen haben, anstatt eine Null zurückzubekommen.

Paddy
quelle
0

Für mich macht es nur Sinn ... Wenn Ihr Selektor keinem Element entspricht, können Sie trotzdem alle normalen jQuery-Prototypfunktionen aufrufen, ohne Fehler zu generieren! Im schlimmsten Fall erhalten Sie ein 'leeres' jQuery-Set, das Ihre Änderungen auf keine Elemente anwendet.

Zwerg
quelle
0

Ich denke, weil es im Front-End verwendet wird - Endbenutzer sollten keine Ausnahmen sehen, weil ein Entwickler einen schlechten Code geschrieben hat. In diesem Fall ist es wahrscheinlich im Allgemeinen sicherer, stillschweigend zu versagen.

PeterJ
quelle
0

Ich dachte, es sollte wie CSS sein. Wenn Sie also versuchen, ein Element zu formatieren, das nicht vorhanden ist, erhalten Sie keinen Fehler.

JD Isaacks
quelle