Sizzle ist eine DOM-Auswahl-Engine, die vom jQuery-Autor John Resig geschrieben wurde. Ihre Geschwindigkeit gilt als die erste in der Branche. Als eigenständige neue Selektor-Engine erschien sie nach der jQuery-Version 1.3 und wurde von John Resig als Open-Source-Projekt übernommen. Sizzle ist ein unabhängiger Teil und nicht von einer Bibliothek abhängig. Wenn Sie jQuery nicht verwenden möchten, können Sie einfach Sizzle verwenden oder es in anderen Frameworks wie Mool, Dojo, YUI usw. verwenden.
Vor ein paar Tagen bereitete ich eine gemeinsame PPT über jQuery vor und fragte meine Kollegen, ob sie neben der Verwendung noch etwas über jQuery wissen wollten. Jemand erwähnte, dass sie wissen wollten, wie der Selektor implementiert ist , und einige Leute erwähnten, wie die Abfragegeschwindigkeit von jQuery im Vergleich zu anderen Frameworks ist. Was die Geschwindigkeit betrifft, können Sie Testbeispiele von der offiziellen Website von Sizzle herunterladen. Die Geschwindigkeit ist in der Tat sehr vorteilhaft. Aber warum es eine so effiziente Laufgeschwindigkeit hat, hängt mit dem Implementierungsprinzip zusammen, das ich hier diskutieren möchte.
Bevor Sie Sizzle verstehen, müssen Sie zunächst verstehen, was sein Selektor ist. Hier ist ein einfaches Beispiel. Schüler, die mit jQuery vertraut sind, müssen auch mit diesem Selektorformat vertraut sein:
Es filtert grundsätzlich gründlich von links nach rechts, um passende DOM-Elemente zu finden. Diese Anweisung ist nicht allzu kompliziert. Es ist nicht schwierig, diese Abfrageanweisung selbst zu implementieren. Allerdings haben Abfrageanweisungen nur Grundregeln und keine feste Anzahl und Reihenfolge von Selektoren. Wie können wir uns an diese willkürliche Anordnung und Kombination anpassen, wenn wir unseren eigenen Code schreiben? Sizzle kann eine normale Analyse und Ausführung verschiedener Situationen erreichen.
Der Quellcode von Sizzle ist in der Tat kompliziert und seine Ideen sind schwer zu verstehen. Lassen wir die äußeren Verpackungsschichten beiseite und schauen wir uns direkt die drei Methoden an, die meiner Meinung nach den Kern der gesamten Implementierung ausmachen:
Die erste Kernmethode. In Zeile 1052 des Quellcodes gibt es eine Tokenize-Funktion:
Der zweite Parameter parseOnly ist falsch, was bedeutet, dass nur der Token-Serialisierungsvorgang ausgeführt wird und kein Ergebnis zurückgegeben wird. In diesem Fall wird das Serialisierungsergebnis zur späteren Verwendung zwischengespeichert. Selector ist die Abfrageanweisung.
Wenn nach der Verarbeitung durch diese Funktion beispielsweise selector="#idtag.class, a:first" übergeben wird, können Sie ein Ergebnis ähnlich dem folgenden Format erhalten:
[ [ {matches:" id ",type:"ID"}, {matches:" tag ",type:"TAG"}, {matches:" class ",type:"CLASS"}, ... ], [ {matches:" a",type:"TAG"}, ... ], […], … ]
Wenn ich den Namen der Tokenize-Funktion und ihre Funktion sehe, fällt mir leicht das Wort „Kompilierungsprinzip“ ein. Es ähnelt hier ein wenig einer lexikalischen Analyse, aber diese lexikalische Analyse ist einfacher als die lexikalische Analyse, die beim Kompilieren des Programms durchgeführt wird.
Die tokenize-Methode führt eine „Wortsegmentierung“ basierend auf dem regulären Ausdruck von Kommas, Leerzeichen und relationalen Selektoren im Selektor durch und erhält ein zweidimensionales Array (bitte erlauben Sie mir, diesen ungenauen Namen zu verwenden), in dem das erste -dimensionales Array Sie werden durch Kommas getrennt und im Quellcode als Gruppen bezeichnet.
Schauen wir uns den Quellcode noch einmal an. Ab Zeile 405 gibt es eine Definition von Expr = Sizzle.selectors = {}. In Zeile 567 gibt es eine Definition von Filter. Hier finden wir die grundlegenden Filtertypen: „ID ", "TAG", "CLASS", "ATTR", "CHILD", "PSEUDO", das sind die Typen, die schließlich durch tokenize klassifiziert werden.
Nachdem die „Wortsegmentierung“ abgeschlossen ist, schauen Sie sich noch den in Zeile 405 definierten Ausdruck Expr= Sizzle.selectors = {} an. Alle uns bekannten Selektoren sind hier zu finden, und jeder Selektor entspricht einer Methodendefinition. An diesem Punkt sollten Sie darüber nachdenken, ob Sizzle tatsächlich eine „Wortsegmentierung“ für den Selektor durchführt, ihn aufteilt und dann die entsprechenden Methoden von Expr findet, um bestimmte Abfrage- oder Filtervorgänge durchzuführen
Die Antwort lautet im Grunde ja. Aber Sizzle hat einen spezifischeren und clevereren Ansatz. Schauen wir uns die zweite Methode an, die meiner Meinung nach sehr wichtig ist:In Zeile 1293 des Quellcodes gibt es eine matcherFromTokens-Funktion:
Die übergebenen Parameter werden von der tokenize-Methode abgerufen. Matcher kann als „Matching-Programm“ verstanden werden. Im wahrsten Sinne des Wortes besteht die Funktion dieser Funktion darin, ein Matching-Programm durch Token zu generieren. Tatsächlich ist es so. Aus Platzgründen werden in diesem Artikel nur einige der Implementierungsprinzipien von Sizzle vorgestellt, die ich verstehe, und der Quellcode wird nicht veröffentlicht. Wenn ich später Zeit habe, werde ich möglicherweise einen ausführlicheren Artikel zur Quellcodeanalyse zusammenstellen.
Die matcherFromTokens-Methode bestätigt die vorherige Annahme. Sie fungiert als Verbindung und Verknüpfung zwischen dem Selektor „Wortsegmentierung“ und der in Expr definierten Matching-Methode. Man kann sagen, dass verschiedene Permutationen und Kombinationen von Selektoren anpassbar sind. Das Clevere an Sizzle ist, dass es die erhaltenen „Wortsegmentierungs“-Ergebnisse nicht direkt einzeln mit den Methoden in Expr abgleicht und diese einzeln ausführt, sondern zunächst eine große Matching-Methode gemäß den Regeln kombiniert und ausführt im letzten Schritt. Aber wie es nach der Kombination ausgeführt wird, hängt von der dritten Schlüsselmethode ab:
In Zeile 1350 des Quellcodes gibt es eine superMatcher-Methode:
Diese Methode ist keine direkt definierte Methode, sondern wird über die Methode matcherFromGroupMatchers(elementMatchers, setMatchers) in Zeile 1345 zurückgegeben, spielt jedoch eine wichtige Rolle bei der endgültigen Ausführung.
Die superMatcher-Methode bestimmt einen Startabfragebereich basierend auf den Parametern Seed, ExpandContext und Context. Es kann direkt aus dem Seed abgefragt und gefiltert werden, oder er kann innerhalb des Kontexts oder des übergeordneten Knotenbereichs des Kontexts liegen. Wenn es nicht beim Startwert beginnt, führt es zunächst den Code Expr.find["TAG"]( "*", expandContext && context.parentNode || context ) aus und wartet auf eine elems-Sammlung (Array). Führen Sie dann eine Durchquerung der Elemente durch und verwenden Sie die vorgenerierte Matcher-Methode, um die Elemente einzeln abzugleichen. Wenn das Ergebnis wahr ist, werden die Elemente direkt in die zurückgegebene Ergebnismenge gestapelt.
Okay, da die ursprünglichen Ergebnisse der Matcher-Methode hier alle Bool-Werte sind, gehen wir zurück zu Zeile 405 und werfen einen Blick auf die im Filter in Expr enthaltenen Methoden. Sie geben alle Bool-Werte zurück. Das Einbeziehen weiterer Pseudoklassenmethoden, die PSEUDO (Pseudoklasse) entsprechen, ist dasselbe. Es scheint ein wenig subversiv gegenüber meiner ursprünglichen Idee zu sein. Es stellt sich heraus, dass es nicht darum geht, Schicht für Schicht nach unten zu prüfen, sondern ein bisschen so, als würde man hin und her gehen, um Abgleich und Filter durchzuführen. In Expr geben nur find und preFilter Rückgabesätze zurück.
Obwohl ich immer noch Zweifel daran habe, warum für die Ergebnismenge Einzelabgleichs- und Filtermethoden verwendet werden, denke ich, dass das grundlegendste „Kompilierungsprinzip“ von Sizzle klar hätte erklärt werden müssen.
Aber wir dürfen keine Zweifel aufkommen lassen, machen wir weiter. Tatsächlich scheint dieser Artikel selbst rückwärts geschrieben zu sein. Studierende, die sich den Quellcode ansehen möchten, werden diese drei Schlüsselmethoden zu Beginn nicht sehen. Tatsächlich erledigt Sizzle noch eine Reihe anderer Aufgaben, bevor diese drei Methoden eingegeben werden.
Der eigentliche Eingang zu Sizzle befindet sich in Zeile 220 des Quellcodes:
Der erste Absatz dieser Methode ist relativ einfach zu verstehen. Wenn der Selektor einem einzelnen ID-Selektor (#id) zugeordnet werden kann, kann das Element direkt mithilfe der Methode context.getElementById(m) gefunden werden Ausweis. Wenn der Selektor einem einzelnen TAG-Selektor zugeordnet werden kann, verwenden Sie zunächst die Methode context.getElementsByTagName(selector), um die relevanten Elemente zu finden. Wenn der aktuelle Browser nur natives getElementsByClassName verwendet und der übereinstimmende Selektor ein einzelner CLASS-Selektor ist, wird auch die Methode context.getElementsByClassName(m) verwendet, um die relevanten Elemente zu finden. Diese drei Methoden sind alle native Methoden, die von Browsern unterstützt werden, und ihre Ausführungseffizienz ist definitiv die höchste.
Wenn die grundlegendsten Methoden nicht verfügbar sind, geben Sie die Auswahlmethode ein. Zeile 1480 des Quellcodes hat seine Definition:
Bei der Auswahlmethode wird die zuvor erwähnte Operation „Wortsegmentierung“ zuerst am Selektor ausgeführt. Nach diesem Vorgang haben wir jedoch nicht direkt mit der Zusammenstellung der Matching-Methode begonnen, sondern zunächst einige Suchvorgänge durchgeführt. Die Suchoperation kann hier der Suchoperation in Expr entsprechen. Sie führt eine Abfrageoperation aus und gibt eine Ergebnismenge zurück.
Es versteht sich, dass select den durch „Wortsegmentierung“ erhaltenen Selektor verwendet, um zunächst die Ergebnismenge herauszufinden, die mit der Suchmethode entsprechend ihrem Typ durchsucht werden kann. Bei der Suchoperation wird die Ergebnismenge in der Reihenfolge der Selektoren von links nach rechts eingegrenzt. Wenn nach einem Durchlauf alle Selektoren im Selektor die Suchoperation ausführen können, wird das Ergebnis direkt zurückgegeben. Andernfalls wird der zuvor beschriebene „Kompilierungs-“ und Filtervorgang gestartet.
An dieser Stelle können Sie auch vorbeikommen und den Arbeitsablauf von Sizzle grundsätzlich verstehen. Die oben verbleibenden Fragen sind zu diesem Zeitpunkt eigentlich keine Fragen mehr, da bei der umgekehrten Matching-Filterung der Suchbereich bereits der kleinste Satz ist, der Schicht für Schicht gefiltert wurde. Die Reverse-Matching-Filtermethode ist tatsächlich eine effiziente Wahl für die Selektoren, denen sie entspricht, beispielsweise Pseudoklassen.
Fassen wir kurz zusammen, warum Sizzle so effizient ist.
Zuallererst In Bezug auf den Verarbeitungsablauf wird immer zuerst die effizienteste native Methode für die Verarbeitung verwendet. Was zuvor eingeführt wurde, ist nur die eigene Selektor-Implementierungsmethode von Sizzle. Wenn Sizzle tatsächlich ausgeführt wird, ermittelt es zunächst, ob der aktuelle Browser die native Methode querySelectorAll unterstützt (Quellcodezeile 1545). Wenn diese Methode unterstützt wird, wird sie zuerst verwendet. Die vom Browser nativ unterstützte Methode ist definitiv effizienter als die von Sizzles eigenen js geschriebene Methode. (Weitere Informationen zu querySelectorAll finden Sie online.) Wenn die Methode querySelectorAll nicht unterstützt wird, legt Sizzle auch Priorität darauf, zu bestimmen, ob getElementById, getElementsByTag, getElementsByClassName und andere Methoden direkt zur Lösung des Problems verwendet werden können.
Zweitens In relativ komplexen Situationen entscheidet sich Sizzle immer dafür, zuerst native Methoden zum Abfragen und Auswählen von so viel wie möglich zu verwenden, um den Auswahlbereich einzugrenzen, und dann das zuvor eingeführte „Kompilierungsprinzip“ zum Einschränken zu verwenden Der Auswahlbereich wird einzeln abgeglichen und gefiltert. Der Arbeitsablauf im Schritt „Kompilierung“ ist etwas kompliziert und die Effizienz wird definitiv etwas geringer sein als bei den vorherigen Methoden, aber Sizzle versucht, diese Methoden so wenig wie möglich zu verwenden, und das versucht es gleichzeitig auch Machen Sie die mit diesen Methoden verarbeiteten Ergebnismengen so klein und einfach wie möglich, um eine höhere Effizienz zu erzielen.
Noch einmal : Selbst nach dem Eintritt in diesen „Kompilierungsprozess“ hat Sizzle auch einen Caching-Mechanismus implementiert, den wir vorübergehend ignoriert und nicht eingeführt haben, um den Prozess zunächst zu erklären. Zeile 1535 des Quellcodes nennen wir den Eintrag „compilation“, was bedeutet, dass er die dritte Kernmethode superMatcher aufruft. Verfolgen Sie die Zeile und sehen Sie sich Zeile 1466 an. Die Kompilierungsmethode speichert die auf der Grundlage des Selektors generierte Matching-Funktion zwischen. Darüber hinaus sehen Sie sich die tokenize-Methode in Zeile 1052 an. Sie speichert tatsächlich die Ergebnisse der Wortsegmentierung basierend auf dem Selektor zwischen. Mit anderen Worten, nachdem wir die Sizzle-Methode (Selektor) einmal ausgeführt haben und die Sizzle-Methode (Selektor) beim nächsten Mal direkt aufrufen, wird der darin enthaltene „Kompilierungsprozess“ mit dem höchsten Leistungsverbrauch nicht zu viel Leistung verbrauchen, und der vorherige Cache wird dies tun direkt abgerufen werden. Ich denke, dass einer der größten Vorteile der sogenannten „Kompilierung“ darin bestehen könnte, dass sie das Caching erleichtert. Unter der sogenannten „Kompilierung“ kann hier auch das Generieren vorverarbeiteter Funktionen und deren Speicherung für die spätere Verwendung verstanden werden.
An dieser Stelle hoffe ich, dass ich grundsätzlich die Fragen von Studierenden beantworten kann, die sich bisher Gedanken über die Implementierungsprinzipien und die Ausführungseffizienz von Selektoren gemacht haben. Darüber hinaus basiert die Analyse-Schlussfolgerung dieses Artikels auf dem Quellcode der neuesten Version von Sizzle. Die im Artikel genannten Codezeilennummern unterliegen dem Quellcode dieser Version, der unter http heruntergeladen werden kann. //sizzlejs.com/. Die Zeit ist knapp, also haben Sie bitte Nachsicht, wenn es bei der Analyse zu Unvollkommenheiten kommt. Studierende, die noch Fragen haben, können gerne offline weiter kommunizieren.
Das Obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, er gefällt Ihnen allen.