One place for hosting & domains

      Verstehen

      Nginx Server- und Standortblockauswahlalgorithmen verstehen


      Einführung

      Nginx ist einer der beliebtesten Webserver der Welt. Er kann hohe Lasten mit vielen gleichzeitigen Clientverbindungen erfolgreich bewältigen und kann problemlos als Webserver, Mailserver oder Reverse-Proxy-Server fungieren.

      In diesem Leitfaden werden wir einige der Details hinter den Kulissen erörtern, die bestimmen, wie Nginx Client-Abfragen verarbeitet. Das Verständnis dieser Ideen kann das Rätselraten beim Entwerfen von Server- und Standortblöcken erleichtern und die Bearbeitung von Abfragen weniger unvorhersehbar erscheinen lassen.

      Nginx-Blockkonfigurationen

      Nginx unterteilt die Konfigurationen, die unterschiedliche Inhalte bereitstellen sollen, logisch in Blöcke, die in einer hierarchischen Struktur leben. Bei jedem Abfragen einer Client-Abfrage beginnt Nginx einen Prozess zur Feststellung, welche Konfigurationsblöcke zur Bearbeitung der Abfrage verwendet werden sollen. Dieser Entscheidungsprozess ist das, was wir in diesem Leitfaden diskutieren werden.

      Die Hauptblöcke, die wir diskutieren werden, sind der Server-Block und der Standort-Block.

      Ein Serverblock ist eine Teilmenge der Nginx-Konfiguration, die einen virtuellen Server definiert, der zur Verarbeitung von Abfragen eines definierten Typs verwendet wird. Administratoren konfigurieren häufig mehrere Serverblöcke und entscheiden anhand des angeforderten Domainnamens, Ports und der IP-Adresse, welcher Block welche Verbindung verarbeiten soll.

      Ein Standortblock befindet sich in einem Serverblock und wird verwendet, um zu definieren, wie Nginx Abfragen für verschiedene Ressourcen und URIs für den übergeordneten Server verarbeiten soll. Der URI-Bereich kann nach Belieben des Administrators mithilfe dieser Blöcke unterteilt werden. Es ist ein extrem flexibles Modell.

      Wie Nginx entscheidet, welcher Serverblock eine Abfrage verarbeiten wird

      Da der Administrator mit Nginx mehrere Serverblöcke definieren kann, die als separate virtuelle Webserverinstanzen fungieren, muss festgelegt werden, welcher dieser Serverblöcke zur Erfüllung einer Abfrage verwendet wird.

      Dies geschieht durch ein definiertes System von Überprüfungen, mit denen die bestmögliche Übereinstimmung gefunden wird. Die wichtigsten Serverblock-Direktiven, mit denen sich Nginx während dieses Prozesses befasst, sind die Direktive listen und server_name.

      Analysieren der „listen“-Direktive, um mögliche Übereinstimmungen zu finden

      Zunächst überprüft Nginx die IP-Adresse und den Port der Abfrage. Dies wird mit der listen-Direktive jedes Servers verglichen, um eine Liste der Serverblöcke zu erstellen, die möglicherweise die Abfrage auflösen können.

      Die listen-Direktive definiert typischerweise, auf welche IP-Adresse und welchen Port der Serverblock reagieren wird. Standardmäßig erhält jeder Serverblock, der keine listen-Direktive enthält, die listen-Parameter 0.0.0.0:80 (oder 0.0.0.0:8080, wenn Nginx von einem normalen non-root-Benutzer ausgeführt wird). Auf diese Weise können diese Blöcke auf Abfragen an einer beliebigen Schnittstelle an Port 80 antworten, aber dieser Standardwert hat im Serverauswahlprozess nicht viel Gewicht.

      Die listen-Direktive kann auf Folgendes eingestellt werden:

      • Eine Kombination aus IP-Adresse und Port.
      • Eine einzelne IP-Adresse, die dann den Standardport 80 überwacht.
      • Ein einzelner Port, der jede Schnittstelle an diesem Port überwacht.
      • Der Pfad zu einem Unix-Socket.

      Die letzte Option hat im Allgemeinen nur Auswirkungen beim Übergeben von Abfragen zwischen verschiedenen Servern.

      Wenn Sie versuchen, zu bestimmen, an welchen Serverblock eine Abfrage gesendet wird, wird Nginx zunächst versuchen, anhand der Spezifität der listen-Direktive mit den folgenden Regeln zu entscheiden:

      • Nginx übersetzt alle „unvollständigen“ listen-Direktiven, indem fehlende Werte mit den Standardwerten ersetzt werden, sodass jeder Block durch seine IP-Adresse und den Port bewertet werden kann. Einige Beispiele für diese Übersetzungen sind:
        • Ein Block ohne listen-Direktive verwendet den Wert 0.0.0.0:80.
        • Ein Block, der auf eine IP-Adresse 111.111.111.111 ohne Port festgelegt ist, wird zu 111.111.111.111:80
        • Ein Block, der auf Port 8888 ohne IP-Adresse festgelegt ist, wird zu 0.0.0.0:8888
      • Nginx versucht dann, eine Liste der Serverblöcke zu sammeln, die der Abfrage am spezifischsten entsprechen, basierend auf der IP-Adresse und dem Port. Dies bedeutet, dass ein Block, der funktional 0.0.0.0 als IP-Adresse verwendet (um mit einer beliebigen Schnittstelle übereinzustimmen), nicht ausgewählt wird, wenn übereinstimmende Blöcke vorhanden sind, in denen eine bestimmte IP-Adresse aufgeführt ist. In jedem Fall muss der Port genau übereinstimmen.
      • Wenn nur eine spezifischste Übereinstimmung vorhanden ist, wird dieser Serverblock verwendet, um die Abfrage zu bedienen. Wenn mehrere Serverblöcke mit derselben Spezifitätsübereinstimmung vorhanden sind, beginnt Nginx mit der Auswertung der Direktive server_name jedes Serverblocks.

      Es ist wichtig, zu verstehen, dass Nginx die Direktive server_name nur dann auswertet, wenn zwischen Serverblöcken unterschieden werden muss, die der gleichen Spezifitätsstufe in der listen-Direktive entsprechen. Wenn beispielsweise example.com auf Port 80 von 192.168.1.10 gehostet wird, wird eine Abfrage für example.com in diesem Beispiel trotz der Direktive server_name im zweiten Block immer vom ersten Block bedient.

      server {
          listen 192.168.1.10;
      
          . . .
      
      }
      
      server {
          listen 80;
          server_name example.com;
      
          . . .
      
      }
      

      Für den Fall, dass mehr als ein Serverblock mit gleicher Spezifität übereinstimmt, besteht der nächste Schritt darin, die Direktive server_name zu überprüfen.

      Analysieren der Direktive „server_name“, um eine Übereinstimmung auszuwählen

      Um Abfragen mit gleichermaßen spezifischen listen-Direktiven weiter auszuwerten, überprüft Nginx die „Host“-Überschrift der Abfrage. Dieser Wert enthält die Domain oder IP-Adresse, die der Client tatsächlich versucht hat, zu erreichen.

      Nginx versucht, die beste Übereinstimmung für den gefundenen Wert zu finden, indem es sich die Direktive server_name in jedem der Serverblöcke ansieht, die noch Auswahlkandidaten sind. Nginx bewertet diese durch die folgende Formel:

      • Nginx versucht zunächst, einen Serverblock mit einem server_name zu finden, der dem Wert in der „Host“-Überschrift der Abfrage genau entspricht. Wenn dieser gefunden wird, wird der zugeordnete Block verwendet, um die Abfrage zu bedienen. Wenn mehrere genaue Übereinstimmungen gefunden werden, wird die erste verwendet.
      • Wenn keine genaue Übereinstimmung gefunden wird, versucht Nginx, einen Serverblock mit einem server_name zu finden, der mit einem führenden Platzhalter übereinstimmt (angezeigt durch ein * am Anfang des Namens in der Konfiguration). Wenn eine gefunden wird, wird der zugeordnete Block verwendet, um die Abfrage zu bedienen. Wenn mehrere Übereinstimmungen gefunden werden, wird die längste Übereinstimmung verwendet, um die Abfrage zu bedienen.
      • Wenn mit einem führenden Platzhalter keine Übereinstimmung gefunden wird, sucht Nginx nach einem Serverblock mit einem server_name, der mit einem nachfolgenden Platzhalter übereinstimmt (angezeigt durch einen Servernamen, der in der Konfiguration mit einem * endet). Wenn eine gefunden wird, wird dieser Block verwendet, um die Abfrage zu bedienen. Wenn mehrere Übereinstimmungen gefunden werden, wird die längste Übereinstimmung verwendet, um die Abfrage zu bedienen.
      • Wenn mit einem nachgestellten Platzhalter keine Übereinstimmung gefunden wird, wertet Nginx Serverblöcke aus, die den server_name mithilfe regulärer Ausdrücke definieren (angezeigt durch ein ~ vor dem Namen). Der erste server_name mit einem regulären Ausdruck, der der „Host“-Überschrift entspricht, wird zur Bearbeitung der Abfrage verwendet.
      • Wenn keine Übereinstimmung mit regulären Ausdrücken gefunden wird, wählt Nginx den Standardserverblock für diese IP-Adresse und diesen Port aus.

      Jede Kombination aus IP-Adresse und Port verfügt über einen Standardserverblock, der verwendet wird, wenn mit den oben genannten Methoden keine Vorgehensweise festgelegt werden kann. Bei einer Kombination aus IP-Adresse und Port ist dies entweder der erste Block in der Konfiguration oder der Block, der die Option default_server als Teil der listen-Direktive enthält (die den zuerst gefundenen Algorithmus überschreiben würde). Pro IP-Adresse/Port-Kombination kann nur eine default_server-Deklaration vorhanden sein.

      Beispiele

      Wenn ein server_name definiert ist, der genau mit dem „Host“-Überschriftswert übereinstimmt, wird dieser Serverblock ausgewählt, um die Abfrage zu verarbeiten.

      Wenn in diesem Beispiel die „Host“-Überschrift der Abfrage auf „host1.example.com“ gesetzt wäre, würde der zweite Server ausgewählt:

      server {
          listen 80;
          server_name *.example.com;
      
          . . .
      
      }
      
      server {
          listen 80;
          server_name host1.example.com;
      
          . . .
      
      }
      

      Wenn keine genaue Übereinstimmung gefunden wird, prüft Nginx, ob ein server_name mit einem passenden Start-Platzhalter vorhanden ist. Die längste Übereinstimmung mit einem Platzhalter wird ausgewählt, um die Abfrage zu erfüllen.

      Wenn in diesem Beispiel die Abfrage einer „Host“-Überschrift „www.example.org“ hat, würde der zweite Serverblock ausgewählt:

      server {
          listen 80;
          server_name www.example.*;
      
          . . .
      
      }
      
      server {
          listen 80;
          server_name *.example.org;
      
          . . .
      
      }
      
      server {
          listen 80;
          server_name *.org;
      
          . . .
      
      }
      

      Wenn mit einem Start-Platzhalter keine Übereinstimmung gefunden wird, prüft Nginx anhand eines Platzhalters am Ende des Ausdrucks, ob eine Übereinstimmung vorliegt. An diesem Punkt wird die längste Übereinstimmung mit einem Platzhalter ausgewählt, um die Abfrage zu bedienen.

      Wenn beispielsweise die Abfrage eine „Host“-Überschrift auf „www.example.com“ festgelegt hat, wird der dritte Serverblock ausgewählt:

      server {
          listen 80;
          server_name host1.example.com;
      
          . . .
      
      }
      
      server {
          listen 80;
          server_name example.com;
      
          . . .
      
      }
      
      server {
          listen 80;
          server_name www.example.*;
      
          . . .
      
      }
      

      Wenn keine Platzhalterübereinstimmungen gefunden werden können, versucht Nginx, Übereinstimmungen mit den Direktiven server_name zuzuordnen, die reguläre Ausdrücke verwenden. Der erste übereinstimmende reguläre Ausdruck wird ausgewählt, um auf die Abfrage zu antworten.

      Wenn beispielsweise die „Host“-Überschrift der Abfrage auf „www.example.com“ gesetzt ist, wird der zweite Serverblock ausgewählt, um die Abfrage zu erfüllen:

      server {
          listen 80;
          server_name example.com;
      
          . . .
      
      }
      
      server {
          listen 80;
          server_name ~^(www|host1).*.example.com$;
      
          . . .
      
      }
      
      server {
          listen 80;
          server_name ~^(subdomain|set|www|host1).*.example.com$;
      
          . . .
      
      }
      

      Wenn keiner der oben genannten Schritte die Abfrage erfüllen kann, wird die Abfrage an den Standardserver für die übereinstimmende IP-Adresse und den passenden Port weitergeleitet.

      Übereinstimmung von Standortblöcken

      Ähnlich wie bei dem Prozess, mit dem Nginx den Serverblock auswählt, der eine Abfrage verarbeitet, verfügt Nginx auch über einen etablierten Algorithmus zur Entscheidung, welcher Standortblock innerhalb des Servers zur Verarbeitung von Abfragen verwendet werden soll.

      Standortblock-Syntax

      Bevor wir uns damit befassen, wie Nginx entscheidet, welcher Standortblock zur Verarbeitung von Abfragen verwendet werden soll, gehen wir einen Teil der Syntax durch, der möglicherweise in Standortblockdefinitionen angezeigt wird. Standortblöcke befinden sich in Serverblöcken (oder anderen Standortblöcken) und werden verwendet, um zu entscheiden, wie der Abfrage-URI (der Teil der Abfrage, der nach dem Domainnamen oder der IP-Adresse/dem IP-Port kommt) verarbeitet werden soll.

      Standortblöcke haben im Allgemeinen die folgende Form:

      location optional_modifier location_match {
      
          . . .
      
      }
      

      Das oben angezeigte location_match definiert, gegen was Nginx den Abfrage-URI prüfen soll. Das Vorhandensein oder Nichtvorhandensein des Modifikators im obigen Beispiel beeinflusst die Art und Weise, wie der Nginx versucht, mit dem Standortblock übereinzustimmen. Die folgenden Modifikatoren bewirken, dass der zugehörige Standortblock wie folgt interpretiert wird:

      • (none): Wenn keine Modifikatoren vorhanden sind, wird der Speicherort als Präfix-Übereinstimmung interpretiert. Dies bedeutet, dass der angegebene Speicherort mit dem Beginn des Abfrage-URIs abgeglichen wird, um eine Übereinstimmung zu ermitteln.
      • =: Wenn ein Gleichheitszeichen verwendet wird, wird dieser Block als Übereinstimmung betrachtet, wenn der Abfrage-URI genau mit dem angegebenen Standort übereinstimmt.
      • ~: Wenn ein Tilde-Modifikator vorhanden ist, wird dieser Speicherort als Übereinstimmung zwischen regulären Ausdrücken und Groß- und Kleinschreibung interpretiert.
      • ~*: Wenn ein Tilde- und ein Sternchen-Modifikator verwendet werden, wird der Positionsblock als Übereinstimmung zwischen regulären Ausdrücken ohne Berücksichtigung der Groß- und Kleinschreibung interpretiert.
      • ^~: Wenn ein Karat- und Tilde-Modifikator vorhanden ist und dieser Block als beste Übereinstimmung mit nicht regulären Ausdrücken ausgewählt ist, findet keine Übereinstimmung mit regulären Ausdrücken statt.

      Beispiele zur Demonstration der Standortblock-Syntax

      Als Beispiel für die Präfixübereinstimmung kann der folgende Standortblock ausgewählt werden, um auf Abfrage-URIs zu antworten, die wie /site, /site/page1/index.html oder /site/index.html aussehen:

      location /site {
      
          . . .
      
      }
      

      Zur Demonstration der genauen Übereinstimmung der Abfrage-URI wird dieser Block immer verwendet, um auf eine Abfrage-URI zu antworten, die wie /page1 aussieht. Es wird nicht verwendet, um auf eine /page1/index.html-Abfrage-URI zu antworten. Beachten Sie, dass bei Auswahl dieses Blocks und Erfüllung der Abfrage über eine Indexseite eine interne Umleitung an einen anderen Speicherort erfolgt, der der eigentliche Handhaber der Abfrage ist:

      location = /page1 {
      
          . . .
      
      }
      

      Als Beispiel für einen Speicherort, der als regulärer Ausdruck mit Groß- und Kleinschreibung interpretiert werden sollte, kann dieser Block verwendet werden, um Abfragen für /tortoise.jpg zu verarbeiten, nicht jedoch für /FLOWER.PNG:

      location ~ .(jpe?g|png|gif|ico)$ {
      
          . . .
      
      }
      

      Ein Block, der eine Übereinstimmung ohne Berücksichtigung der Groß- und Kleinschreibung ähnlich wie oben ermöglichen würde, ist unten dargestellt. Hier könnten sowohl /tortoise.jpg als auch /FLOWER.PNG von diesem Block verarbeitet werden:

      location ~* .(jpe?g|png|gif|ico)$ {
      
          . . .
      
      }
      

      Schließlich würde dieser Block verhindern, dass eine Übereinstimmung mit regulären Ausdrücken auftritt, wenn festgestellt wird, dass dies die beste Übereinstimmung mit nicht regulären Ausdrücken ist. Er könnte Abfragen für /costumes/ninja.html verarbeiten:

      location ^~ /costumes {
      
          . . .
      
      }
      

      Wie Sie sehen, geben die Modifikatoren an, wie der Standortblock interpretiert werden soll. Dies sagt uns jedoch nicht, welchen Algorithmus Nginx verwendet, um zu entscheiden, an welchen Standortblock die Abfrage gesendet werden soll. Wir werden das als nächstes durchgehen.

      Wie Nginx wählt, welcher Standort zur Bearbeitung von Abfragen verwendet werden soll

      Nginx wählt den Speicherort aus, an dem eine Abfrage bearbeitet wird, ähnlich wie bei der Auswahl eines Serverblocks. Es wird ein Prozess durchlaufen, der den besten Standortblock für eine bestimmte Abfrage ermittelt. Das Verständnis dieses Prozesses ist eine entscheidende Anforderung, um Nginx zuverlässig und korrekt konfigurieren zu können.

      Unter Berücksichtigung der oben beschriebenen Arten von Standortdeklarationen bewertet Nginx die möglichen Standortkontexte, indem der Abfrage-URI mit jedem der Standorte verglichen wird. Dies geschieht mit dem folgenden Algorithmus:

      • Nginx überprüft zunächst alle Präfix-basierten Standortübereinstimmungen (alle Standorttypen, die keinen regulären Ausdruck enthalten). Es vergleicht jeden Standort mit dem vollständigen Abfrage-URI.
      • Zunächst sucht Nginx nach einer genauen Übereinstimmung Wenn ein Standortblock mit dem Modifikator = gefunden wird, der genau mit dem Abfrage-URI übereinstimmt, wird dieser Standortblock sofort ausgewählt, um die Abfrage zu bedienen.
      • Wenn keine genauen (mit dem Modifikator =) Positionsblockübereinstimmungen gefunden werden, fährt Nginx mit der Auswertung nicht exakter Präfixe fort. Es ermittelt den am längsten übereinstimmenden Präfixspeicherort für den angegebenen Abfrage-URI, den es dann wie folgt auswertet:
        • Wenn der am längsten übereinstimmende Präfixstandort den Modifikator ^ ~ hat, beendet Nginx die Suche sofort und wählt diesen Standort aus, um die Abfrage zu bearbeiten.
        • Wenn die Position mit dem längsten übereinstimmenden Präfix nicht den Modifikator ^ ~ verwendet, wird die Übereinstimmung für den Moment von Nginx gespeichert, damit der Fokus der Suche verschoben werden kann.
      • Nachdem die Position mit der längsten Übereinstimmung ermittelt und gespeichert wurde, fährt Nginx mit der Auswertung der Positionen für reguläre Ausdrücke fort (sowohl Groß- und Kleinschreibung als auch Nicht-Groß-/Kleinschreibung beachten). Wenn sich Positionen mit regulären Ausdrücken innerhalb der am längsten übereinstimmenden Präfixposition befinden, verschiebt Nginx diese an den Anfang der Liste der zu überprüfenden Regex-Positionen. Nginx versucht dann, nacheinander mit den Positionen der regulären Ausdrücke abzugleichen. Der erste reguläre Ausdruck des Standortes wird sofort ausgewählt, um die Abfrage zu bedienen.
      • Wenn keine Standorte für reguläre Ausdrücke gefunden werden, die mit dem Abfrage-URI übereinstimmen, wird der zuvor gespeicherte Präfixstandort ausgewählt, um die Abfrage zu bedienen.

      Es ist wichtig, zu verstehen, dass Nginx standardmäßig Übereinstimmungen mit regulären Ausdrücken anstelle von Präfixübereinstimmungen bereitstellt. Es werden jedoch zuerst Präfixpositionen ausgewertet, sodass die Verwaltung diese Tendenz überschreiben kann, indPositionen mit den Modifikatoren = und ^ ~ angegeben werden.

      Es ist auch wichtig, zu beachten, dass, während Präfixpositionen im Allgemeinen basierend auf der längsten, spezifischsten Übereinstimmung ausgewählt werden, die Auswertung regulärer Ausdrücke gestoppt wird, wenn die erste übereinstimmende Position gefunden wird. Dies bedeutet, dass die Positionierung innerhalb der Konfiguration enorme Auswirkungen auf die Positionen regulärer Ausdrücke hat.

      Schließlich ist es wichtig, zu verstehen, dass Übereinstimmungen mit regulären Ausdrücken innerhalb der längsten Präfixübereinstimmung „die Zeile überspringen“, wenn Nginx Regex-Positionen auswertet. Diese werden der Reihe nach ausgewertet, bevor andere Übereinstimmungen mit regulären Ausdrücken berücksichtigt werden. Maxim Dounin, ein unglaublich hilfreicher Nginx-Entwickler, erklärt in diesem Beitrag diesen Teil des Auswahlalgorithmus.

      Wann springt die Standortblockbewertung zu anderen Standorten?

      Wenn ein Standortblock ausgewählt wird, um eine Abfrage zu bedienen, wird die Abfrage im Allgemeinen von diesem Punkt an vollständig in diesem Kontext behandelt. Nur der ausgewählte Standort und die geerbten Anweisungen bestimmen, wie die Abfrage verarbeitet wird, ohne dass Geschwisterstandortblöcke eingreifen.

      Obwohl dies eine allgemeine Regel ist, mit der Sie Ihre Standortblöcke auf vorhersehbare Weise entwerfen können, ist es wichtig zu wissen, dass es manchmal Zeiten gibt, in denen eine neue Standortsuche durch bestimmte Anweisungen innerhalb des ausgewählten Standortes ausgelöst wird. Die Ausnahmen von der Regel „Nur ein Standortblock“ können Auswirkungen auf die tatsächliche Zustellung der Abfrage haben und stimmen möglicherweise nicht mit den Erwartungen überein, die Sie beim Entwerfen Ihrer Standortblöcke hatten.

      Einige Direktiven, die zu dieser Art der internen Weiterleitung führen können, sind:

      • index
      • try_files
      • rewrite
      • error_page

      Gehen wir diese kurz durch.

      Die Direktive index führt immer zu einer internen Weiterleitung, wenn sie verwendet wird, um die Abfrage zu verarbeiten. Genaue Standortübereinstimmungen werden häufig verwendet, um den Auswahlprozess zu beschleunigen, indem die Ausführung des Algorithmus sofort beendet wird. Wenn Sie jedoch eine genaue Standortübereinstimmung mit einem Verzeichnis vornehmen, besteht eine gute Chance, dass die Abfrage zur tatsächlichen Verarbeitung an einen anderen Standort umgeleitet wird.

      In diesem Beispiel wird der erste Standort mit einem Abfrage-URI von /exact abgeglichen. Um die Abfrage zu verarbeiten, initiiert die vom Block geerbte Indexanweisung eine interne Umleitung zum zweiten Block:

      index index.html;
      
      location = /exact {
      
          . . .
      
      }
      
      location / {
      
          . . .
      
      }
      

      Wenn Sie im obigen Fall wirklich die Ausführung benötigen, um im ersten Block zu bleiben, müssen Sie eine andere Methode finden, um die Abfrage an das Verzeichnis zu erfüllen. Sie können beispielsweise einen ungültigen index für diesen Block festlegen und den autoindex aktivieren:

      location = /exact {
          index nothing_will_match;
          autoindex on;
      }
      
      location  / {
      
          . . .
      
      }
      

      Dies ist eine Möglichkeit, um zu verhindern, dass ein index den Kontext wechselt, ist jedoch für die meisten Konfigurationen wahrscheinlich nicht hilfreich. Meistens kann eine genaue Übereinstimmung mit Verzeichnissen hilfreich sein, um beispielsweise die Abfrage neu zu schreiben (was auch zu einer neuen Standortsuche führt).

      Eine andere Instanz, in der der Verarbeitungsort neu bewertet werden kann, ist die Direktive try_files. Diese Direktive weist Nginx an, das Vorhandensein einer benannten Gruppe von Dateien oder Verzeichnissen zu überprüfen. Der letzte Parameter kann ein URI sein, zu dem Nginx eine interne Umleitung vornimmt.

      Erwägen Sie folgende Konfiguration:

      root /var/www/main;
      location / {
          try_files $uri $uri.html $uri/ /fallback/index.html;
      }
      
      location /fallback {
          root /var/www/another;
      }
      

      Wenn im obigen Beispiel eine Anfrage für /blahblah gestellt wird, erhält der erste Standort zunächst die Abfrage. Er wird versuchen, eine Datei namens blahblah im Verzeichnis /var/www/main zu finden. Wenn gefunden werden kann, wird anschließend nach einer Datei mit dem Namen blahblah.html gesucht. Anschließend wird versucht, festzustellen, ob sich im Verzeichnis /var/www/main ein Verzeichnis mit dem Namen blahblah/ befindet. Wenn alle diese Versuche fehlschlagen, wird zu /fallback/index.html umgeleitet. Dies löst eine weitere Standortsuche aus, die vom zweiten Standortblock abgefangen wird. Dies wird die Datei /var/www/anderen/fallback/index.html bereitstellen.

      Eine weitere Direktive, die dazu führen kann, dass ein Standortblock übergeben wird, ist die Direktive rewrite. Wenn Sie den letzten Parameter mit der Direktive rewrite zum Umschreiben oder überhaupt keinen Parameter verwenden, sucht Nginx basierend auf den Ergebnissen des Umschreibens nach einem neuen übereinstimmenden Standort.

      Wenn wir beispielsweise das letzte Beispiel so ändern, dass es ein Umschreiben enthält, können wir feststellen, dass die Abfrage manchmal direkt an den zweiten Standort übergeben wird, ohne sich auf die Direktive try_files zu verlassen:

      root /var/www/main;
      location / {
          rewrite ^/rewriteme/(.*)$ /$1 last;
          try_files $uri $uri.html $uri/ /fallback/index.html;
      }
      
      location /fallback {
          root /var/www/another;
      }
      

      Im obigen Beispiel wird eine Abfrage für /rewriteme/hello zunächst vom ersten Standortblock verarbeitet. Sie wird in /hello umgeschrieben und ein Standort gesucht. In diesem Fall stimmt sie wieder mit dem ersten Standort überein und wird wie gewohnt von den try_files verarbeitet. Wenn nichts gefunden wird, kehren Sie möglicherweise zu /fallback/index.html zurück (mithilfe der oben beschriebenen internen Umleitung try_files).

      Wenn jedoch eine Abfrage für /rewriteme/fallback/hello gestellt wird, stimmt der erste Block erneut überein. Das Umschreiben wird erneut angewendet, diesmal mit /fallback/hello. Die Abfrage wird dann aus dem zweiten Standortblock heraus zugestellt.

      Eine verwandte Situation tritt bei der Direktive return auf, wenn die Statuscodes 301 oder 302 gesendet werden. Der Unterschied in diesem Fall ist, dass es eine völlig neue Abfrage in Form einer extern sichtbaren Umleitung bildet. Dieselbe Situation kann bei der Direktive rewrite auftreten, wenn die Flags redirect oder permanent verwendet werden. Diese Standortsuche sollte jedoch nicht unerwartet sein, da extern sichtbare Weiterleitungen immer zu einer neuen Abfrage führen.

      Die Direktive error_page kann zu einer internen Umleitung führen, die der von try_files erstellten ähnelt. Diese Direktive wird verwendet, um zu definieren, was passieren soll, wenn bestimmte Statuscodes aufgetreten sind. Dies wird wahrscheinlich nie ausgeführt, wenn try_files festgelegt ist, da diese Direktive den gesamten Lebenszyklus einer Abfrage behandelt.

      Erwägen Sie dieses Beispiel:

      root /var/www/main;
      
      location / {
          error_page 404 /another/whoops.html;
      }
      
      location /another {
          root /var/www;
      }
      

      Jede Abfrage (außer denjenigen, die mit /another beginnen) wird vom ersten Block bearbeitet, der Dateien aus /var/www/main bereitstellt. Wenn jedoch keine Datei gefunden wird (Status 404), erfolgt eine interne Umleitung zu /another/whoops.html, die zu einer neuen Standortsuche führt, die schließlich im zweiten Block landet. Diese Datei wird aus /var/www/another/whoops.html. bereitgestellt.

      Wie Sie sehen können, kann das Verständnis der Umstände, unter denen Nginx eine neue Standortsuche auslöst, dazu beitragen, das Verhalten vorherzusagen, das bei Abfragen auftreten wird.

      Zusammenfassung

      Wenn Sie wissen, wie Nginx Client-Abfragen verarbeitet, können Sie Ihre Arbeit als Administrator erheblich vereinfachen. Sie können anhand jeder Client-Abfrage wissen, welchen Serverblock Nginx auswählt. Sie können auch anhand der Abfrage-URI festlegen, wie der Standortblock ausgewählt wird. Wenn Sie wissen, wie Nginx verschiedene Blöcke auswählt, können Sie die Kontexte verfolgen, die Nginx anwenden wird, um jede Abfrage zu bearbeiten.



      Source link

      Verstehen relationaler Datenbanken


      Einführung

      Datenbankmanagementsysteme (DBMS) sind Computerprogramme, mit denen Benutzer mit einer Datenbank interagieren können. Ein DBMS ermöglicht es Benutzern, den Zugriff auf eine Datenbank zu steuern, Daten zu schreiben, Abfragen auszuführen und andere Aufgaben im Zusammenhang mit der Datenbankverwaltung durchzuführen.

      Um eine dieser Aufgaben auszuführen, muss das DBMS jedoch eine Art zugrunde liegendes Modell haben, das definiert, wie die Daten organisiert sind. Das relationale Modell ist ein Ansatz zur Organisation von Daten, der in der Datenbanksoftware seit seiner Entwicklung in den späten 60er Jahren breite Verwendung gefunden hat, sodass zum Zeitpunkt der Erstellung dieses Artikels vier der fünf beliebtesten DBMS relational sind.

      Dieser konzeptionelle Artikel skiziert die Geschichte des relationalen Modells, wie relationale Datenbanken Daten organisieren und wie sie heute verwendet werden.

      Geschichte des relationalen Modells

      Datenbanken sind logisch modellierte Cluster von Informationen oder Daten. Jede Sammlung von Daten ist eine Datenbank, unabhängig davon, wie oder wo sie gespeichert ist. Sogar ein Aktenschrank mit Lohn- und Gehaltsabrechnungsinformationen ist eine Datenbank, ebenso wie ein Stapel von Krankenhauspatientenformularen oder die Sammlung von Kundeninformationen eines Unternehmens, die über mehrere Standorte verteilt sind. Bevor die Speicherung und Verwaltung von Daten mit Computern gängige Praxis war, waren physische Datenbanken wie diese die einzigen Datenbanken, die Behörden und Unternehmen zur Speicherung von Informationen zur Verfügung standen.

      Um die Mitte des 20. Jahrhunderts führten Entwicklungen in der Informatik zu Computern mit mehr Verarbeitungsleistung sowie größerer lokaler und externer Speicherkapazität. Diese Fortschritte führten dazu, dass Computerwissenschaftler begannen, das Potenzial für die Speicherung und Verwaltung immer größerer Datenmengen zu erkennen.

      Es gab jedoch keine Theorien darüber, wie Computer Daten auf sinnvolle, logische Weise organisieren können. Es ist eine Sache, unsortierte Daten auf einem Computer zu speichern, aber es ist viel komplizierter, Systeme zu entwerfen, die es erlauben, diese Daten auf konsistente, praktische Weise hinzuzufügen, abzurufen, zu sortieren und anderweitig zu verwalten. Der Bedarf an einem logischen Rahmen zur Speicherung und Organisation von Daten führte zu einer Reihe von Vorschlägen, wie Computer für die Datenverwaltung nutzbar gemacht werden können.

      Ein frühes Datenbankmodell war das hierarchische Modell, bei dem die Daten in einer baumartigen Struktur organisiert sind, ähnlich wie bei modernen Dateisystemen. Das folgende Beispiel zeigt, wie das Layout eines Teils einer hierarchischen Datenbank zur Kategorisierung von Tieren aussehen könnte:

      Beispiel einer hierarchischen Datenbank: Kategorisierung von Tieren

      Das hierarchische Modell wurde in den frühen Datenbankmanagementsystemen verbreitet eingesetzt, erwies sich aber auch als etwas unflexible. Obwohl in diesem Modell einzelne Datensätze mehrere „untergeordnete Datensätze“ haben können, kann jeder Datensatz nur einen „übergeordneten“ Datensatz in der Hierarchie haben. Aus diesem Grund waren diese früheren hierarchischen Datenbanken darauf beschränkt, nur „Eins-zu-Eins“ und „Eins-zu-Viele“-Beziehungen darzustellen. Dieser Mangel an „Viele-zu-Viele“-Beziehungen könnte zu Problemen führen, wenn Sie mit Datenpunkten arbeiten, die Sie mit mehr als einem übergeordneten Datensatz verknüpfen möchten.

      In den späten 1960er Jahren entwickelte Edgar F. Codd, ein Informatiker, der bei IBM arbeitete, das relationale Modell der Datenbankverwaltung. Das relationale Modell von Codd ermöglichte es, einzelne Datensätze mit mehr als einer Tabelle zu verknüpfen, wodurch zusätzlich zu den „Eins-zu-Viele“-Beziehungen auch „Viele-zu-Viele“-Beziehungen zwischen Datenpunkten möglich wurden. Dies bot mehr Flexibilität als andere existierende Modelle, wenn es um die Gestaltung von Datenbankstrukturen ging, und bedeutetet, dass relationale Datenbankmanagementsysteme (RDBMS) ein wesentlich breiteres Spektrum von Geschäftsanforderungen erfüllen können.

      Codd schlug eine Sprache zur Verwaltung relationaler Daten vor, bekannt als Alpha, die die Entwicklung späterer Datenbanksprachen beeinflusste. Zwei Kollegen von Codd bei IBM, Donald Chamberlin und Raymond Boyce, schufen eine solche Sprache, die von Alpha inspiriert ist. Sie nannten ihre Sprache SEQUEL, kurz für Structured English Query Language, aber aufgrund eines bestehenden Warenzeichens kürzten sie den Namen ihrer Sprache auf SQL (formal eher als Structured Query Language bezeichnet).

      Aufgrund von Hardware-Beschränkungen waren die frühen relationalen Datenbanken noch ungemein langsam, und es dauerte einige Zeit, bis die Technologie weit verbreitet war. Aber Mitte der 1980er Jahre war das relationale Modell von Codd bereits in einer Reihe kommerzieller Datenbankmanagementprodukte sowohl von IBM als auch seinen Konkurrenten implementiert worden. Diese Anbieter folgten ebenfalls dem IBM-Vorbild, indem sie ihre eigenen Dialekte von SQL entwickelten und implementierten. Bis 1987 hatten sowohl das American Standards Institute als auch die International Organization for Standardization Standards für SQL ratifiziert und veröffentlicht und damit den Status von SQL als akzeptierte Sprache für die Verwaltung von RDBMS gefestigt.

      Die weite Verwendung des relationalen Modells in mehreren Branchen führte dazu, dass es als Standardmodell für das Datenmanagement anerkannt wurde. Selbst mit dem Aufkommen verschiedener NoSQL-Datenbanken in den letzten Jahren bleiben relationale Datenbanken die dominierenden Werkzeuge zur Speicherung und Organisation von Daten.

      Wie relationale Datenbanken Daten organisieren

      Nachdem Sie nun ein allgemeines Verständnis für die Geschichte des relationalen Modells haben, lassen Sie uns einen genaueren Blick darauf werfen, wie das Modell Daten organisiert.

      Die grundlegendsten Elemente des relationalen Modells sind Beziehungen, die von Benutzern und modernen RDBMS als Tabellen erkannt werden. Eine Beziehung ist ein Satz von Tupeln oder Zeilen in einer Tabelle, wobei jedes Tupel einen Satz von Attributen oder Spalten gemeinsam hat:

      Diagrammbeispiel, wie Beziehungen, Tupel und Attribute miteinander verknüpft sind

      Eine Spalte ist die kleinste Organisationsstruktur einer relationalen Datenbank und stellt die verschiedenen Facetten dar, die die Datensätze in der Tabelle definieren. Daher ihr formellerer Name, Attribute. Sie können sich jedes Tupel als eine einzigartige Instanz jeder Art von Personen, Objekten, Ereignissen oder Assoziationen vorstellen, die die Tabelle enthält. Diese Instanzen können z. B. Mitarbeiter eines Unternehmens, Verkäufe aus einem Online-Geschäft oder Labor-Testergebnisse sein. In einer Tabelle, die beispielsweise Mitarbeiterdaten von Lehrern an einer Schule enthält, können die Tupel Attribute wie name, subjects, start_date usw. haben.

      Bei der Erstellung von Spalten geben Sie einen Datentyp an, der festlegt, welche Art von Einträge in dieser Spalte zulässig sind. RDBMS implementieren oft ihre eigenen eindeutigen Datentypen, die möglicherweise nicht direkt mit ähnlichen Datentypen in anderen Systemen austauschbar sind. Einige gängige Datentypen umfassen Datumsangaben, Zeichenketten, Ganzzahlen und Boolesche.

      Im relationalen Modell enthält jede Tabelle mindestens eine Spalte, die zur eindeutigen Identifizierung jeder Zeile verwendet werden kann, was als Primärschlüssel bezeichnet wird. Dies ist wichtig, da es bedeutet, dass Benutzer nicht wissen müssen, wo ihre Daten physisch auf einem Computer gespeichert sind; stattdessen können ihre DBMS jeden Datensatz verfolgen und ad hoc zurückgeben. Dies wiederum bedeutet, dass die Datensätze keine definierte logische Reihenfolge haben und die Benutzer die Möglichkeit haben, ihre Daten in beliebiger Reihenfolge oder durch beliebige Filter zurückgeben.

      Wenn Sie zwei Tabellen haben, die Sie miteinander verknüpfen möchten, können Sie dies unter anderem mit einem Fremdschlüssel tun. Ein Fremdschlüssel ist im Wesentlichen eine Kopie des Primärschlüssels einer Tabelle (der „übergeordneten“ Tabelle), der in eine Spalte einer anderen Tabelle (der „untergeordneten“ Tabelle) eingefügt wird. Das folgende Beispiel verdeutlicht die Beziehung zwischen zwei Tabellen, von denen die eine zur Aufzeichnung von Informationen über die Mitarbeiter eines Unternehmens und die andere zur Verfolgung der Verkäufe des Unternehmens verwendet wird. In diesem Beispiel wird der Primärschlüssel der Tabelle EMPLOYEES als Fremdschlüssel der Tabelle SALES verwendet:

      Diagrammbeispiel, wie der Primärschlüssel der Tabelle EMPLOYEES als Fremdschlüssel der Tabelle SALES fungiert

      Wenn Sie versuchen, der untergeordneten Tabelle einen Datensatz hinzuzufügen, und der in die Fremdschlüsselspalte eingegebene Wert im Primärschlüssel der übergeordneten Tabelle nicht existiert, ist die Einfügeanweisung ungültig. Dies hilft, die Integrität der Beziehungsebene aufrechtzuerhalten, da die Zeilen in beiden Tabellen immer korrekt zueinander in Beziehung stehen werden.

      Die Strukturelemente des relationalen Modells tragen dazu bei, die Daten auf organisierte Weise zu speichern, aber die Speicherung von Daten ist nur dann sinnvoll, wenn Sie diese auch abrufen können. Um Informationen aus einem RDBMS abzurufen, können Sie eine *Abfrage *oder eine strukturierte Anfrage nach einem Satz von Informationen stellen. Wie zuvor erwähnt, verwenden die meisten relationalen Datenbanken SQL zur Verwaltung und Abfrage von Daten. SQL ermöglicht es Ihnen, Abfrageergebnisse mit einer Vielzahl von Klauseln, Prädikaten und Ausdrücken zu filtern und zu manipulieren, wodurch Sie eine genaue Kontrolle darüber erhalten, welche Daten im Ergebnissatz angezeigt werden.

      Vorteile und Grenzen relationaler Datenbanken

      Lassen Sie uns mit Blick auf die zugrunde liegende Organisationsstruktur relationaler Datenbanken einige ihrer Vor- und Nachteile betrachten.

      Heute weichen sowohl SQL als auch die Datenbanken, die es implementieren, in mehrfacher Hinsicht von Codds relationalem Modell ab. Beispielsweise schreibt das Modell von Codd vor, dass jede Zeile in einer Tabelle eindeutig sein sollte, während die meisten modernen relationalen Datenbanken aus Gründen der Praktikabilität duplizierte Zeilen zulassen. Es gibt einige, die SQL-Datenbanken nicht als echte relationale Datenbanken betrachten, wenn sich nicht an jede von Codds Spezifikationen für das relationale Modell halten. In der Praxis wird jedoch jedes DBMS, das SQL verwendet und sich zumindest teilweise an das relationale Modell hält, als relationales Datenbankmanagementsystem bezeichnet.

      Obwohl relationale Datenbanken schnell an Popularität gewannen, wurden einige Unzulänglichkeiten des relationalen Modells offensichtlich, als Daten immer wertvoller wurden und Unternehmen begannen, mehr davon zu speichern. Zum einen kann es schwierig sein, eine relationale Datenbank horizontal zu skalieren. Horizontale Skalierung oder Herausskalieren ist die Praxis des Hinzufügens weiterer Computer zu einem bestehenden Stack, um die Last zu verteilen und mehr Datenverkehr und eine schnellere Verarbeitung zu ermöglichen. Dies steht oft im Gegensatz zur vertikalen Skalierung, bei der die Hardware eines vorhandenen Servers aufgerüstet wird, in der Regel durch Hinzufügen von mehr RAM oder CPU.

      Der Grund dafür, dass die horizontale Skalierung einer relationalen Datenbank schwierig ist, hängt damit zusammen, dass das relationale Modell auf Konsistenz ausgelegt ist, d, h. Clients, die dieselbe Datenbank abfragen, werden immer dieselben Daten abrufen. Wenn Sie eine relationale Datenbank horizontal über mehrere Computer skalieren, wird es schwierig, die Konsistenz zu gewährleisten, da Clients Daten auf einen Knoten Schreiben können, aber nicht auf die anderen. Es gäbe wahrscheinlich eine Verzögerung zwischen dem anfänglichen Schreiben und dem Zeitpunkt, zu dem die anderen Knoten aktualisiert werden, um die Änderungen widerspiegeln, was zu Inkonsistenzen zwischen ihnen führen würde.

      Eine weitere Einschränkung bei RDBMS besteht darin, dass das relationale Modell für die Verwaltung strukturierter Daten oder von Daten konzipiert wurde, die mit einem vordefinierten Datentyp übereinstimmen oder zumindest auf eine vorher festgelegte Weise organisiert sind, sodass sie leicht sortierbar und durchsuchbar sind. Mit der Verbreitung von Personal Computing und dem Aufkommen des Internets in den frühen 1990er Jahren wurden jedoch unstrukturierte Daten – wie E-Mail-Nachrichten, Fotos, Videos usw. – immer üblicher.

      All dies bedeutet nicht, dass relationale Datenbanken nicht nützlich sind. Ganz im Gegenteil, das relationale Modell ist auch nach über 40 Jahren noch immer der dominierende Rahmen für das Datenmanagement. Ihre Verbreitung und Langlebigkeit bedeuten, das relationale Datenbanken eine ausgereifte Technologie darstellen, was wiederum einer ihrer wichtigsten Vorteile ist. Es gibt viele Anwendungen, die für die Arbeit mit dem relationalen Modell konzipiert wurden, sowie viele Datenbankadministratoren, die in ihrer Laufbahn Experten auf dem Gebiet der relationalen Datenbanken sind. Für diejenigen, die mit relationalen Datenbanken beginnen möchten, gibt es ein breites Angebot an Ressourcen, in gedruckter Form und online.

      Ein weiterer Vorteil relationaler Datenbanken besteht darin, dass fast jedes RDBMS Transaktionen unterstützt. Eine Transaktion besteht aus einer oder mehreren einzelnen SQL-Anweisungen, die nacheinander als eine einzige Arbeitseinheit ausgeführt werden. Transaktionen stellen einen Alles-oder-Nichts-Ansatz dar, was bedeutet, dass jede SQL-Anweisung in der Transaktion gültig sein muss, da ansonsten die gesamte Transaktion fehlschlägt. Dies ist sehr hilfreich, um die Datenintegrität zu gewährleisten, wenn Änderungen an mehreren Zeilen oder Tabellen vorgenommen werden.

      Und schließlich sind relationale Datenbanken äußerst flexibel. Sie wurden zum Aufbau einer Vielzahl unterschiedlicher Anwendungen verwendet und arbeiten auch bei sehr großen Datenmengen weiterhin effizient. SQL ist ebenfalls extrem leistungsfähig, sodass Sie im Handumdrehen Daten hinzufügen und ändern sowie die Struktur von Datenbankschemata und Tabellen ändern können, ohne die vorhandenen Daten zu beeinträchtigen.

      Zusammenfassung

      Dank ihrer Flexibilität und ihres Designs für Datenintegrität sind relationale Datenbanken auch mehr als fünfzig Jahre nach ihrer ersten Konzeption immer noch die wichtigste Art und Weise, wie Daten verwaltet und gespeichert werden. Selbst mit dem Aufkommen verschiedener NoSQL-Datenbanken in den letzten Jahren sind das Verständnis des relationalen Modells und die Arbeit mit RDBMS der Schlüssel für jeden, der Anwendungen entwickeln möchte, die die Datenleistung nutzen.

      Um mehr über einige beliebte Open-Source-RDBMS zu erfahren, empfehlen wir Ihnen, sich unseren Vergleich verschiedener relationaler Open-Source-Datenbanken anzusehen. Wenn Sie mehr über Datenbanken im Allgemeinen erfahren möchten, empfehlen wir Ihnen, einen Blick in unsere vollständige Bibliothek datenbankbezogener Inhalte zu werfen.



      Source link

      Verstehen von Destrukturierung, Rest-Parametern und Spread-Syntax in JavaScript


      Der Autor hat den COVID-19 Relief Fund dazu ausgewählt, eine Spende im Rahmen des Programms Write for DOnations zu erhalten.

      Einführung

      Seit der Ausgabe 2015 der ECMAScript-Spezifikation wurden der JavaScript-Sprache viele neue Funktionalitäten für die Arbeit mit Arrays und Objekten zur Verfügung gestellt. Einige der bemerkenswerten, die Sie in diesem Artikel kennenlernen, sind Destrukturierung, Rest-Parameter und Spread-Syntax. Diese Funktionalitäten bieten direkte Zugriffsmöglichkeiten auf die Elemente eines Arrays oder Objekts und können das Arbeiten mit diesen Datenstrukturen schneller und prägnanter machen.

      Viele andere Sprachen verfügen nicht über die entsprechende Syntax für Destrukturierung, Rest-Parameter und Spread. Daher können diese Funktionalitäten sowohl für neue JavaScript-Entwickler als auch für Entwickler aus einer anderen Sprache eine Lernkurve aufweisen. In diesem Artikel erfahren Sie, wie Sie Objekte und Arrays destrukturieren, wie Sie den Spread-Operator zum Auspacken von Objekten und Arrays verwenden und wie Sie Rest-Parameter in Funktionsaufrufen nutzen.

      Destrukturierung

      Die Destrukturierungszuweisung ist eine Syntax, mit der Sie Objekteigenschaften oder Array-Elemente als Variablen zuweisen können. Dadurch können die zur Manipulation von Daten in diesen Strukturen erforderlichen Codezeilen erheblich reduziert werden. Es gibt zwei Arten von Destrukturierung: Objekt-Destrukturierung und Array-Destrukturierung.

      Objekt-Destrukturierung

      Mit der Objekt-Destrukturierung können Sie neue Variablen mit einer Objekteigenschaft als Wert erstellen.

      Betrachten Sie dieses Beispiel eines Objekts, das eine Notiz mit einer id, einem title und einem date darstellt:

      const note = {
        id: 1,
        title: 'My first note',
        date: '01/01/1970',
      }
      

      Bisher mussten Sie zur Erstellung einer neuen Variable für jede Eigenschaft jede Variable mit einer Vielzahl von Wiederholungen einzeln zuweisen:

      // Create variables from the Object properties
      const id = note.id
      const title = note.title
      const date = note.date
      

      Mit der Objekt-Destrukturierung kann das alles in einer Zeile erfolgen. Durch Umgeben der Variablen mit geschweiften Klammern {} erstellt JavaScript aus jeder Eigenschaft neue Variablen mit dem gleichen Namen:

      // Destructure properties into variables
      const { id, title, date } = note
      

      Protokollieren Sie nun die neuen Variablen mit console.log():

      console.log(id)
      console.log(title)
      console.log(date)
      

      Sie erhalten die ursprünglichen Eigenschaftswerte als Ausgabe:

      Output

      1 My first note 01/01/1970

      Anmerkung: Das Destrukturieren eines Objekts ändert das ursprüngliche Objekt nicht. Sie könnten immer noch die ursprüngliche note mit all ihren Einträgen aufrufen.

      Die Standardzuweisung für die Objekt-Destrukturierung erstellt neue Variablen mit dem gleichen Namen wie die Objekteigenschaft. Wenn Sie nicht möchten, dass der Name der neuen Variable und der Eigenschaftsname gleich sind, können Sie die neue Variable auch umbenennen. Verwenden Sie dazu einen Doppelpunkt (:), um einen neuen Namen festzulegen, wie im Folgenden mit noteId zu sehen ist:

      // Assign a custom name to a destructured value
      const { id: noteId, title, date } = note
      

      Protokollieren Sie die neue Variable noteId in der Konsole:

      console.log(noteId)
      

      Sie erhalten die folgende Ausgabe:

      Output

      1

      Sie können auch geschachtelte Objektwerte destrukturieren. Aktualisieren Sie beispielsweise das note-Objekt, um ein geschachteltes author-Objekt zu erhalten:

      const note = {
        id: 1,
        title: 'My first note',
        date: '01/01/1970',
        author: {
          firstName: 'Sherlock',
          lastName: 'Holmes',
        },
      }
      

      Sie können nun note destrukturieren und dann zur Erstellung von Variablen aus den author-Eigenschaften erneut destrukturieren:

      // Destructure nested properties
      const {
        id,
        title,
        date,
        author: { firstName, lastName },
      } = note
      

      Als Nächstes protokollieren Sie die neuen Variablen firstName und lastName unter Verwendung von Template-Buchstabensymbolen:

      console.log(`${firstName} ${lastName}`)
      

      Dadurch erhalten Sie folgenden Output:

      Output

      Sherlock Holmes

      Beachten Sie, dass Sie in diesem Beispiel zwar Zugriff auf den Inhalt des author-Objekts haben, das author-Objekt selbst jedoch nicht zugänglich ist. Um auf ein Objekt und seine geschachtelten Werte zuzugreifen, müssten Sie sie getrennt deklarieren:

      // Access object and nested values
      const {
        author,
        author: { firstName, lastName },
      } = note
      
      console.log(author)
      

      Dieser Code gibt das author-Objekt aus:

      Output

      {firstName: "Sherlock", lastName: "Holmes"}

      Die Destrukturierung eines Objekts ist nicht nur nützlich, um die Menge des zu schreibenden Codes zu reduzieren, sondern ermöglicht es Ihnen auch, gezielt auf die Eigenschaften zuzugreifen, die Ihnen wichtig sind.

      Zu guter Letzt kann Destrukturierung verwendet werden, um auf die Objekteigenschaften primitiver Werte zuzugreifen. Beispielsweise ist String ein globales Objekt für Zeichenfolgen und hat eine length-Eigenschaft:

      const { length } = 'A string'
      

      Dadurch wird die inhärente Längeneigenschaft einer Zeichenfolge gefunden und der length-Variablen gleichgesetzt. Protokollieren Sie length, um zu sehen, ob dies funktioniert:

      console.log(length)
      

      Sie erhalten folgende Ausgabe:

      Output

      8

      Die Zeichenfolge A string wurde hier implizit in ein Objekt konvertiert, um die length-Eigenschaft abzurufen.

      Array-Destrukturierung

      Mit der Array-Destrukturierung können Sie neue Variablen mit einem Array-Element als Wert erstellen. Betrachten Sie dieses Beispiel eines Arrays mit den verschiedenen Teilen eines Datums:

      const date = ['1970', '12', '01']
      

      Arrays in JavaScript behalten garantiert ihre Reihenfolge bei, sodass in diesem Fall der erste Index immer ein Jahr ist, der zweite ein Monat, und so weiter. Mit diesem Wissen können Sie Variablen aus den Elementen im Array erstellen:

      // Create variables from the Array items
      const year = date[0]
      const month = date[1]
      const day = date[2]
      

      Doch dies manuell auszuführen kann viel Platz in Ihrem Code beanspruchen. Mit der Array-Destrukturierung können Sie die Werte aus dem Array der Reihe nach entpacken und sie so ihren eigenen Variablen zuweisen:

      // Destructure Array values into variables
      const [year, month, day] = date
      

      Protokollieren Sie nun die neuen Variablen:

      console.log(year)
      console.log(month)
      console.log(day)
      

      Sie erhalten folgende Ausgabe:

      Output

      1970 12 01

      Werte können übersprungen werden, indem die Destrukturierungssyntax zwischen Kommas leer gelassen wird:

      // Skip the second item in the array
      const [year, , day] = date
      
      console.log(year)
      console.log(day)
      

      Das Ausführen ergibt den Wert von year und day:

      Output

      1970 01

      Auch geschachtelte Arrays können destrukturiert werden. Erstellen Sie zuerst ein geschachteltes Array:

      // Create a nested array
      const nestedArray = [1, 2, [3, 4], 5]
      

      Destrukturieren Sie dann dieses Array und protokollieren Sie die neuen Variablen:

      // Destructure nested items
      const [one, two, [three, four], five] = nestedArray
      
      console.log(one, two, three, four, five)
      

      Sie erhalten die folgende Ausgabe:

      Output

      1 2 3 4 5

      Die Destrukturierungssyntax kann zur Destrukturierung der Parameter in einer Funktion angewendet werden. Um das zu testen, destrukturieren Sie die keys und values aus Object.entries().

      Zuerst deklarieren Sie das note-Objekt:

      const note = {
        id: 1,
        title: 'My first note',
        date: '01/01/1970',
      }
      

      Bei diesem Objekt könnten Sie die Schlüssel-Wert-Paare auflisten, indem Sie Argumente bei der Übergabe an die forEach()-Methode destrukturieren:

      // Using forEach
      Object.entries(note).forEach(([key, value]) => {
        console.log(`${key}: ${value}`)
      })
      

      Sie können dasselbe mit einer for-Schleife erreichen:

      // Using a for loop
      for (let [key, value] of Object.entries(note)) {
        console.log(`${key}: ${value}`)
      }
      

      In beiden Fällen erhalten Sie Folgendes:

      Output

      id: 1 title: My first note date: 01/01/1970

      Objekt-Destrukturierung und Array-Destrukturierung können in einer einzigen Destrukturierungszuweisung kombiniert werden. Auch Standardparameter können mit Destrukturierung verwendet werden, wie in diesem Beispiel, das das Standarddatum auf new Date () festlegt.

      Zuerst deklarieren Sie das note-Objekt:

      const note = {
        title: 'My first note',
        author: {
          firstName: 'Sherlock',
          lastName: 'Holmes',
        },
        tags: ['personal', 'writing', 'investigations'],
      }
      

      Destrukturieren Sie dann das Objekt, wobei Sie auch eine neue date-Variable mit der Standardeinstellung von new Date() festlegen:

      const {
        title,
        date = new Date(),
        author: { firstName },
        tags: [personalTag, writingTag],
      } = note
      
      console.log(date)
      

      console.log(date) gibt dann eine ähnliche Ausgabe wie die folgende aus:

      Output

      Fri May 08 2020 23:53:49 GMT-0500 (Central Daylight Time)

      Wie in diesem Abschnitt gezeigt wird, verleiht die Syntax der Destrukturierungszuweisung JavaScript eine große Flexibilität und ermöglicht Ihnen, prägnanteren Code zu schreiben. Im nächsten Abschnitt sehen Sie, wie die Spread-Syntax zur Erweiterung von Datenstrukturen in ihren einzelnen Dateneinträgen genutzt werden kann.

      Spread

      Die Spread-Syntax (...) ist eine weitere hilfreiche Ergänzung zu JavaScript zum Arbeiten mit Arrays, Objekten und Funktionsaufrufen. Spread ermöglicht das Entpacken oder Erweitern von Objekten und Iterablen (wie beispielsweise Arrays), wodurch flache Kopien von Datenstrukturen erstellt werden können, um die Datenmanipulation zu vereinfachen.

      Spread mit Arrays

      Spread kann allgemeine Aufgaben mit Arrays vereinfachen. Nehmen wir zum Beispiel an, Sie haben zwei Arrays und möchten diese kombinieren:

      // Create an Array
      const tools = ['hammer', 'screwdriver']
      const otherTools = ['wrench', 'saw']
      

      Ursprünglich würden Sie concat() verwenden, um die beiden Arrays zu verketten:

      // Concatenate tools and otherTools together
      const allTools = tools.concat(otherTools)
      

      Sie können nun auch Spread verwenden, um die Arrays in ein neues Array zu entpacken:

      // Unpack the tools Array into the allTools Array
      const allTools = [...tools, ...otherTools]
      
      console.log(allTools)
      

      Diese Ausführung würde Folgendes ergeben:

      Output

      ["hammer", "screwdriver", "wrench", "saw"]

      Das kann bei Unveränderlichkeit besonders hilfreich sein. Sie könnten beispielsweise mit einer App arbeiten, die users in einem Array von Objekten gespeichert hat:

      // Array of users
      const users = [
        { id: 1, name: 'Ben' },
        { id: 2, name: 'Leslie' },
      ]
      

      Sie könnten push verwenden, um das bestehende Array zu ändern und einen neuen Benutzer hinzuzufügen, was die veränderbare Option wäre:

      // A new user to be added
      const newUser = { id: 3, name: 'Ron' }
      
      users.push(newUser)
      

      Das ändert jedoch das user-Array, das wir möglicherweise erhalten möchten.

      Mit Spread können Sie ein neues Array aus dem bestehenden erstellen und am Ende ein neues Element hinzufügen:

      const updatedUsers = [...users, newUser]
      
      console.log(users)
      console.log(updatedUsers)
      

      Das neue Array, updatedUsers, hat den neuen Benutzer, aber das ursprüngliche users-Array bleibt unverändert:

      Output

      [{id: 1, name: "Ben"} {id: 2, name: "Leslie"}] [{id: 1, name: "Ben"} {id: 2, name: "Leslie"} {id: 3, name: "Ron"}]

      Das Erstellen von Datenkopien anstelle der Änderung vorhandener Daten kann helfen, unerwartete Änderungen zu verhindern. Wenn Sie in JavaScript ein Objekt oder Array erstellen und einer anderen Variable zuweisen, erstellen Sie nicht tatsächlich ein neues Objekt – Sie übergeben eine Referenz.

      Nehmen Sie dieses Beispiel, in dem ein Array erstellt und einer anderen Variable zugewiesen wird:

      // Create an Array
      const originalArray = ['one', 'two', 'three']
      
      // Assign Array to another variable
      const secondArray = originalArray
      

      Das Entfernen des letzten Elements des zweiten Arrays ändert das erste:

      // Remove the last item of the second Array
      secondArray.pop()
      
      console.log(originalArray)
      

      Dadurch erhalten Sie folgende Ausgabe:

      Output

      ["one", "two"]

      Mit Spread können Sie eine flache Kopie eines Arrays oder Objekts erstellen. Das bedeutet, dass alle Eigenschaften der oberen Ebene geklont, geschachtelte Objekte jedoch weiterhin per Referenz übergeben werden. Bei einfachen Arrays oder Objekten kann eine flache Kopie völlig ausreichen.

      Wenn Sie den gleichen Beispielcode schreiben, aber das Array mit Spread kopieren, wird das originale Array nicht mehr geändert:

      // Create an Array
      const originalArray = ['one', 'two', 'three']
      
      // Use spread to make a shallow copy
      const secondArray = [...originalArray]
      
      // Remove the last item of the second Array
      secondArray.pop()
      
      console.log(originalArray)
      

      Das Folgende wird in der Konsole protokolliert:

      Output

      ["one", "two", "three"]

      Spread kann auch zur Konvertierung eines set oder jeder anderen iterable in ein Array verwendet werden.

      Erstellen Sie ein neues Set und fügen Sie diesem einige Einträge hinzu:

      // Create a set
      const set = new Set()
      
      set.add('octopus')
      set.add('starfish')
      set.add('whale')
      

      Verwenden Sie als Nächstes den Spread-Operator mit set und protokollieren Sie die Ergebnisse:

      // Convert Set to Array
      const seaCreatures = [...set]
      
      console.log(seaCreatures)
      

      Dadurch ergibt sich Folgendes:

      Output

      ["octopus", "starfish", "whale"]

      Das kann auch nützlich sein, um ein Array aus einer Zeichenfolge zu erstellen:

      const string = 'hello'
      
      const stringArray = [...string]
      
      console.log(stringArray)
      

      Dadurch wird ein Array mit jedem Zeichen als Element in dem Array ausgegeben:

      Output

      ["h", "e", "l", "l", "o"]

      Spread mit Objekten

      Beim Arbeiten mit Objekten kann Spread zum Kopieren und Aktualisieren von Objekten genutzt werden.

      Ursprünglich wurde Object.assign() zum Kopieren eines Objekts genutzt:

      // Create an Object and a copied Object with Object.assign()
      const originalObject = { enabled: true, darkMode: false }
      const secondObject = Object.assign({}, originalObject)
      

      Das secondObject ist nun ein Klon des originalObject.

      Dies wird mit der Spread-Syntax vereinfacht – Sie können ein Objekt flach kopieren, indem Sie es in ein neues verteilen:

      // Create an object and a copied object with spread
      const originalObject = { enabled: true, darkMode: false }
      const secondObject = { ...originalObject }
      
      console.log(secondObject)
      

      Dadurch ergibt sich Folgendes:

      Output

      {enabled: true, darkMode: false}

      Genau wie bei Arrays wird auch hier nur eine flache Kopie erstellt, und verschachtelte Objekte werden weiterhin per Referenz übergeben.

      Das Hinzufügen oder Ändern von Eigenschaften an einem bestehenden Objekt auf unveränderliche Weise wird mit Spread vereinfacht. In diesem Beispiel wird die Eigenschaft isLoggedIn dem user-Objekt hinzugefügt:

      const user = {
        id: 3,
        name: 'Ron',
      }
      
      const updatedUser = { ...user, isLoggedIn: true }
      
      console.log(updatedUser)
      

      Dadurch wird Folgendes ausgegeben:

      Output

      {id: 3, name: "Ron", isLoggedIn: true}

      Beim Aktualisieren von Objekten über Spread ist es wichtig, zu beachten, dass auch jedes geschachtelte Objekt verteilt werden muss. Nehmen wir zum Beispiel an, dass sich im user-Objekt ein geschachteltes organization-Objekt befindet:

      const user = {
        id: 3,
        name: 'Ron',
        organization: {
          name: 'Parks & Recreation',
          city: 'Pawnee',
        },
      }
      

      Wenn Sie versuchen würden , ein neues Element zu organization hinzuzufügen, würde das die bestehenden Felder überschreiben:

      const updatedUser = { ...user, organization: { position: 'Director' } }
      
      console.log(updatedUser)
      

      Dadurch würde sich Folgendes ergeben:

      Output

      id: 3 name: "Ron" organization: {position: "Director"}

      Wenn Veränderlichkeit kein Problem ist, könnte das Feld direkt aktualisiert werden:

      user.organization.position = 'Director'
      

      Da wir aber eine unveränderliche Lösung suchen, können wir das innere Objekt verteilen, um die bestehenden Eigenschaften zu erhalten:

      const updatedUser = {
        ...user,
        organization: {
          ...user.organization,
          position: 'Director',
        },
      }
      
      console.log(updatedUser)
      

      Dadurch ergibt sich Folgendes:

      Output

      id: 3 name: "Ron" organization: {name: "Parks & Recreation", city: "Pawnee", position: "Director"}

      Spread mit Funktionsaufrufen

      Spread kann auch mit Argumenten in Funktionsaufrufen genutzt werden.

      Als Beispiel sehen Sie hier eine multiply-Funktion, die drei Parameter nimmt und sie multipliziert:

      // Create a function to multiply three items
      function multiply(a, b, c) {
        return a * b * c
      }
      

      Normalerweise würden sie drei Werte individuell als Argumente an den Funktionsaufruf wie folgt übergeben:

      multiply(1, 2, 3)
      

      Dies würde Folgendes ergeben:

      Output

      6

      Wenn jedoch alle Werte, die Sie an die Funktion übergeben möchten, bereits in einem Array vorhanden sind, können Sie mit der Spread-Syntax jedes Element in einem Array als Argument verwenden:

      const numbers = [1, 2, 3]
      
      multiply(...numbers)
      

      Dies führt zum gleichen Ergebnis:

      Output

      6

      Anmerkung: Ohne Spread kann dies durch Verwendung von apply() erreicht werden:

      multiply.apply(null, [1, 2, 3])
      

      Dies ergibt:

      Output

      6

      Nachdem Sie nun gesehen haben, wie Spread Ihren Code verkürzen kann, können Sie sich eine andere Verwendung der Syntax ... anstehen: Rest-Parameter.

      Rest-Parameter

      Die letzte Funktionalität, die Sie in diesem Artikel kennenlernen, ist die Rest-Parameter-Syntax. Die Syntax entspricht der von Spread (...), hat aber den gegenteiligen Effekt. Statt ein Array oder Objekt in einzelne Werte zu entpacken, erstellt die Rest-Syntax ein Array mit einer indefiniten Anzahl von Argumenten.

      Wenn wir beispielsweise in der Funktion restTest wollten, dass args ein Array aus einer indefiniten Anzahl von Argumenten ist, könnten wir Folgendes haben:

      function restTest(...args) {
        console.log(args)
      }
      
      restTest(1, 2, 3, 4, 5, 6)
      

      Alle Argumente, die an die Funktion restTest übergeben werden, sind nun im args-Array verfügbar:

      Output

      [1, 2, 3, 4, 5, 6]

      Die Rest-Syntax kann als einziger Parameter oder als letzter Parameter in der Liste genutzt werden. Wenn sie als einziger Parameter genutzt wird, wird sie alle Argumente sammeln. Wenn sie am Ende einer Liste steht, wird sie jedes verbleibende Argument so wie in diesem Beispiel sammeln:

      function restTest(one, two, ...args) {
        console.log(one)
        console.log(two)
        console.log(args)
      }
      
      restTest(1, 2, 3, 4, 5, 6)
      

      Dadurch werden die ersten beiden Argumente einzeln aufgeführt und dann die restlichen in ein Array gruppiert:

      Output

      1 2 [3, 4, 5, 6]

      In älterem Code könnte die Variable arguments genutzt werden, um alle Argumente zu sammeln, die durch eine Funktion übergeben werden:

      function testArguments() {
        console.log(arguments)
      }
      
      testArguments('how', 'many', 'arguments')
      

      Dadurch ergäbe sich folgende Ausgabe:

      Output

      1Arguments(3) ["how", "many", "arguments"]

      Das hat jedoch einige Nachteile. Erstens kann die Variable arguments nicht mit Pfeilfunktionen verwendet werden.

      const testArguments = () => {
        console.log(arguments)
      }
      
      testArguments('how', 'many', 'arguments')
      

      Das würde einen Fehler ergeben:

      Output

      Uncaught ReferenceError: arguments is not defined

      Außerdem ist arguments kein echtes Array und kann Methoden wie map und filter nicht verwenden, ohne zuerst in ein Array konvertiert zu werden. Es sammelt außerdem alle übergebenen Argumente statt nur die verbleibenden Argumente, wie sie im Beispiel restTest(one, two, ...args)​​ zu sehen sind.

      Rest kann auch bei der Destrukturierung von Arrays verwendet werden:

      const [firstTool, ...rest] = ['hammer', 'screwdriver', 'wrench']
      
      console.log(firstTool)
      console.log(rest)
      

      Dies ergibt:

      Output

      hammer ["screwdriver", "wrench"]

      Rest kann auch bei der Destrukturierung von Objekten genutzt werden:

      const { isLoggedIn, ...rest } = { id: 1, name: 'Ben', isLoggedIn: true }
      
      console.log(isLoggedIn)
      console.log(rest)
      

      Mit der folgenden Ausgabe:

      Output

      true {id: 1, name: "Ben"}

      Auf diese Weise bietet die Rest-Syntax effiziente Methoden zur Erfassung einer unbestimmten Anzahl von Elementen.

      Zusammenfassung

      In diesem Artikel haben Sie Destrukturierung, Spread-Syntax und Rest-Parameter kennengelernt. Kurz gefasst:

      • Destrukturierung wird genutzt, um Variablen aus Array-Elementen oder Objekt-Eigenschaften zu erstellen.
      • Spread-Syntax wird genutzt, um Iterable wie Arrays, Objekte und Funktionsaufrufe zu entpacken.
      • Rest-Parameter-Syntax erstellt ein Array aus einer indefiniten Anzahl von Werten.

      Destrukturierung, Rest-Parameter und Spread-Syntax sind nützliche Funktionalitäten in JavaScript, die dazu beitragen, Ihren Code prägnant und sauber zu halten.

      Wenn Sie die Destrukturierung in Aktion sehen möchten, werfen Sie einen Blick auf Anpassen von React-Komponenten mit Props, das diese Syntax verwendet, um Daten zu destrukturieren und sie an benutzerdefinierte Frontend-Komponenten zu übergeben. Wenn Sie mehr über JavaScript erfahren möchten, kehren Sie zu unserer Seite mit der Serie Codieren in JavaScript zurück.



      Source link