Content-Length: 295214 | pFad | http://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/Operator_precedence

Operatorpräzedenz - JavaScript | MDN

Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten. Erfahre mehr über dieses Experiment.

View in English Always switch to English

Operatorpräzedenz

Operatorpräzedenz bestimmt, wie Operatoren im Verhältnis zueinander geparst werden. Operatoren mit höherer Präzedenz werden die Operanden von Operatoren mit niedrigerer Präzedenz.

Probieren Sie es aus

console.log(3 + 4 * 5); // 3 + 20
// Expected output: 23

console.log(4 * 3 ** 2); // 4 * 9
// Expected output: 36

let a;
let b;

console.log((a = b = 5));
// Expected output: 5

Präzedenz und Assoziativität

Betrachten Sie einen Ausdruck, der durch die folgende Darstellung beschreibbar ist, wobei sowohl OP1 als auch OP2 Platzhalter für OPerators sind.

a OP1 b OP2 c

Die obige Kombination hat zwei mögliche Interpretationen:

(a OP1 b) OP2 c
a OP1 (b OP2 c)

Welche die Sprache annimmt, hängt von der Identität von OP1 und OP2 ab.

Haben OP1 und OP2 unterschiedliche Präzedenzstufen (siehe Tabelle unten), geht der Operator mit der höheren Präzedenz zuerst und die Assoziativität spielt keine Rolle. Beachten Sie, wie die Multiplikation höhere Präzedenz als die Addition hat und zuerst ausgeführt wird, obwohl die Addition im Code zuerst geschrieben ist.

js
console.log(3 + 10 * 2); // 23
console.log(3 + (10 * 2)); // 23, because parentheses here are superfluous
console.log((3 + 10) * 2); // 26, because the parentheses change the order

Innerhalb von Operatoren mit derselben Präzedenz gruppiert die Sprache diese nach der Assoziativität. Linksassoziativität (von links nach rechts) bedeutet, dass es als (a OP1 b) OP2 c interpretiert wird, während Rechtsassoziativität (von rechts nach links) bedeutet, dass es als a OP1 (b OP2 c) interpretiert wird. Zuweisungsoperatoren sind rechtsassoziativ, so dass Sie schreiben können:

js
a = b = 5; // same as writing a = (b = 5);

mit dem erwarteten Ergebnis, dass a und b den Wert 5 erhalten. Dies liegt daran, dass der Zuweisungsoperator den Wert zurückgibt, der zugewiesen wird. Zuerst wird b auf 5 gesetzt. Dann wird a ebenfalls auf 5 gesetzt — der Rückgabewert von b = 5, also der rechte Operand der Zuweisung.

Ein weiteres Beispiel ist der einzigartige Exponentialoperator, der rechtsassoziativ ist, wohingegen andere arithmetische Operatoren linksassoziativ sind.

js
const a = 4 ** 3 ** 2; // Same as 4 ** (3 ** 2); evaluates to 262144
const b = 4 / 3 / 2; // Same as (4 / 3) / 2; evaluates to 0.6666...

Operatoren werden zuerst nach Präzedenz und dann bei benachbarten Operatoren mit derselben Präzedenz nach Assoziativität gruppiert. Bei der Mischung von Division und Exponentialoperation kommt die Exponentialoperation immer vor der Division. Zum Beispiel ergibt 2 ** 3 / 3 ** 2 0.8888888888888888, da es dasselbe ist wie (2 ** 3) / (3 ** 2).

Betrachten wir für unäre Präfixoperatoren folgendes Muster:

OP1 a OP2 b

wobei OP1 ein Präfix-Unäroperator und OP2 ein binärer Operator ist. Wenn OP1 eine höhere Präzedenz als OP2 hat, wird es als (OP1 a) OP2 b gruppiert; andernfalls wäre es OP1 (a OP2 b).

js
const a = 1;
const b = 2;
typeof a + b; // Equivalent to (typeof a) + b; result is "number2"

Befindet sich der Unäroperator am zweiten Operanden:

a OP2 OP1 b

Dann muss der binäre Operator OP2 eine niedrigere Präzedenz als der unäre Operator OP1 haben, damit es als a OP2 (OP1 b) gruppiert wird. Zum Beispiel ist das folgende ungültig:

js
function* foo() {
  a + yield 1;
}

Da + eine höhere Präzedenz als yield hat, würde dies zu (a + yield) 1 werden — aber da yield ein reserviertes Wort in Generatorfunktionen ist, wäre dies ein Syntaxfehler. Zum Glück haben die meisten unären Operatoren eine höhere Präzedenz als binäre Operatoren und leiden nicht unter diesem Problem.

Haben wir zwei Präfix-Unäroperator:

OP1 OP2 a

Dann muss der unäre Operator, der näher am Operand steht, OP2, eine höhere Präzedenz als OP1 haben, damit es als OP1 (OP2 a) gruppiert wird. Es ist möglich, es anders zu bekommen und mit (OP1 OP2) a zu enden:

js
async function* foo() {
  await yield 1;
}

Da await eine höhere Präzedenz als yield hat, würde dies zu (await yield) 1 werden, was bedeutet, dass auf eine Kennung namens yield gewartet wird, und dies ist ein Syntaxfehler. Ebenso, wenn Sie new !A; haben, da ! eine niedrigere Präzedenz als new hat, würde dies zu (new !) A, was offensichtlich ungültig ist (dieser Code wäre ohnehin unsinnig zu schreiben, da !A immer einen booleschen Wert und keine Konstruktorfunktion erzeugt).

Für postfix Unäroperatoren (nämlich ++ und --) gelten dieselben Regeln. Zum Glück haben beide Operatoren eine höhere Präzedenz als jeder binäre Operator, so dass die Gruppierung immer wie erwartet ist. Außerdem, da ++ zu einem Wert und nicht zu einer Referenz evaluiert, können Sie keine mehrfachen Inkremente zusammenkettieren.

js
let a = 1;
a++++; // SyntaxError: Invalid left-hand side in postfix operation.

Die Operatorpräzedenz wird rekursiv behandelt. Betrachten Sie zum Beispiel diesen Ausdruck:

js
1 + 2 ** 3 * 4 / 5 >> 6

Zuerst gruppieren wir Operatoren mit unterschiedlichen Präzedenzstufen nach abnehmenden Präzedenzstufen.

  1. Der **-Operator hat die höchste Präzedenz, daher wird er zuerst gruppiert.
  2. Um den **-Ausdruck herum befindet sich * rechts und + links. * hat eine höhere Präzedenz, daher wird es zuerst gruppiert. * und / haben die gleiche Präzedenz, daher gruppieren wir sie vorerst zusammen.
  3. Um den in 2 gruppierten *//-Ausdruck herum, da + eine höhere Präzedenz als >> hat, wird der erstere gruppiert.
js
   (1 + ( (2 ** 3) * 4 / 5) ) >> 6
// │    │ └─ 1. ─┘        │ │
// │    └────── 2. ───────┘ │
// └────────── 3. ──────────┘

Innerhalb der *//-Gruppe, da sie beide linksassoziativ sind, wird der linke Operand gruppiert.

js
   (1 + ( ( (2 ** 3) * 4 ) / 5) ) >> 6
// │    │ │ └─ 1. ─┘     │    │ │
// │    └─│─────── 2. ───│────┘ │
// └──────│───── 3. ─────│──────┘
//        └───── 4. ─────┘

Beachten Sie, dass die Operatorpräzedenz und Assoziativität nur die Ausführungsreihenfolge der Operatoren (die implizite Gruppierung) beeinflussen, aber nicht die Ausführungsreihenfolge der Operanden. Die Operanden werden immer von links nach rechts abgearbeitet. Die höher präzedenten Ausdrücke werden immer zuerst ausgewertet, und ihre Ergebnisse werden dann entsprechend der Reihenfolge der Operatorpräzedenz zusammengesetzt.

js
function echo(name, num) {
  console.log(`Evaluating the ${name} side`);
  return num;
}
// Exponentiation operator (**) is right-associative,
// but all call expressions (echo()), which have higher precedence,
// will be evaluated before ** does
console.log(echo("left", 4) ** echo("middle", 3) ** echo("right", 2));
// Evaluating the left side
// Evaluating the middle side
// Evaluating the right side
// 262144

// Exponentiation operator (**) has higher precedence than division (/),
// but evaluation always starts with the left operand
console.log(echo("left", 4) / echo("middle", 3) ** echo("right", 2));
// Evaluating the left side
// Evaluating the middle side
// Evaluating the right side
// 0.4444444444444444

Wenn Sie mit Binärbäumen vertraut sind, denken Sie daran als an eine Post-Order-Durchlauf.

                /
       ┌────────┴────────┐
echo("left", 4)         **
                ┌────────┴────────┐
        echo("middle", 3)  echo("right", 2)

Nachdem alle Operatoren korrekt gruppiert wurden, würde sich für die binären Operatoren ein Binärbaum ergeben. Die Auswertung beginnt bei der äußersten Gruppe — das ist der Operator mit der niedrigsten Präzedenz (in diesem Fall /). Der linke Operand dieses Operators wird zuerst ausgewertet, was aus höherpräzedenten Operatoren bestehen kann (wie einem Aufrufausdruck echo("left", 4)). Nachdem der linke Operand ausgewertet wurde, wird der rechte Operand in der gleichen Weise ausgewertet. Daher werden alle Blattknoten — die echo()-Aufrufe — von links nach rechts besucht, unabhängig von der Präzedenz der Operatoren, die sie verbinden.

Kurzschlussverhalten

Im vorherigen Abschnitt haben wir gesagt, dass "die höher präzedenten Ausdrücke immer zuerst ausgewertet werden" — das ist im Allgemeinen richtig, muss jedoch mit der Erkenntnis des Kurzschlussverhaltens ergänzt werden, in welchem Fall ein Operand möglicherweise überhaupt nicht ausgewertet wird.

Kurzschlussverhalten ist ein Fachbegriff für bedingte Auswertung. Zum Beispiel wird im Ausdruck a && (b + c), wenn a falsy ist, der Unterausdruck (b + c) nicht einmal ausgewertet, selbst wenn er gruppiert ist und daher eine höhere Präzedenz als && hat. Wir könnten sagen, dass der logische UND-Operator (&&) "kurzgeschlossen" wird. Neben dem logischen UND gehören auch logisches ODER (||), Nullish-Koaleszenz (??) und optionales Chaining (?.) zu den kurzgeschlossenen Operatoren.

js
a || (b * c); // evaluate `a` first, then produce `a` if `a` is "truthy"
a && (b < c); // evaluate `a` first, then produce `a` if `a` is "falsy"
a ?? (b || c); // evaluate `a` first, then produce `a` if `a` is not `null` and not `undefined`
a?.b.c; // evaluate `a` first, then produce `undefined` if `a` is `null` or `undefined`

Bei der Auswertung eines kurzgeschlossenen Operators wird der linke Operand immer ausgewertet. Der rechte Operand wird nur ausgewertet, wenn der linke Operand das Ergebnis der Operation nicht bestimmen kann.

Hinweis: Das Verhalten des Kurzschlusses ist in diese Operatoren eingebaut. Andere Operatoren würden immer beide Operanden auswerten, unabhängig davon, ob das tatsächlich nützlich ist — zum Beispiel wird NaN * foo() immer foo aufrufen, selbst wenn das Ergebnis nie etwas anderes als NaN sein könnte.

Das vorherige Modell der Post-Order-Durchlauf bleibt bestehen. Allerdings entscheidet die Sprache, nach dem Besuch des linken Teilbaums eines kurzgeschlossenen Operators, ob der rechte Operand ausgewertet werden muss. Wenn nicht (zum Beispiel weil der linke Operand von || bereits truthy ist), wird das Ergebnis direkt zurückgegeben, ohne den rechten Teilbaum zu besuchen.

Betrachten Sie diesen Fall:

js
function A() { console.log('called A'); return false; }
function B() { console.log('called B'); return false; }
function C() { console.log('called C'); return true; }

console.log(C() || B() && A());

// Logs:
// called C
// true

Nur C() wird ausgewertet, trotz && höherer Präzedenz. Dies bedeutet nicht, dass || in diesem Fall eine höhere Präzedenz hat — es ist genau weil (B() && A()) eine höhere Präzedenz hat, dass es als Ganzes vernachlässigt wird. Wird es wie folgt neu angeordnet:

js
console.log(A() && B() || C());
// Logs:
// called A
// called C
// true

Dann würde der Kurzschlusseffekt von && nur verhindern, dass B() ausgewertet wird, aber weil A() && B() insgesamt false ist, würde C() immer noch ausgewertet.

Beachten Sie jedoch, dass Kurzschlussverhalten das endgültige Auswertungsergebnis nicht ändert. Es beeinflusst nur die Auswertung der Operanden, nicht wie Operatoren gruppiert werden — wenn die Auswertung der Operanden keine Nebeneffekte hat (zum Beispiel Ausgabe ins Konsolenprotokoll, Zuweisungen zu Variablen, Werfen eines Fehlers), wäre das Kurzschlussverhalten überhaupt nicht beobachtbar.

Die Zuweisungsgegenstücke dieser Operatoren (&&=, ||=, ??=) sind ebenfalls kurzgeschlossen. Sie sind so kurzgeschlossen, dass die Zuweisung überhaupt nicht stattfindet.

Tabelle

Die folgende Tabelle listet Operatoren in der Reihenfolge von höchster Präzedenz (18) bis niedrigster Präzedenz (1) auf.

Einige allgemeine Anmerkungen zur Tabelle:

  1. Nicht alle hier enthaltenen Syntaxen sind im strengen Sinne "Operatoren". Zum Beispiel werden Spread ... und Pfeil => typischerweise nicht als Operatoren betrachtet. Wir haben sie jedoch dennoch aufgenommen, um zu zeigen, wie eng sie im Vergleich zu anderen Operatoren/Ausdrucken binden.
  2. Einige Operatoren haben bestimmte Operanden, die Ausdrücke erfordern, die schmaler sind als jene, die von höheren Präzedenzoperatoren produziert werden. Zum Beispiel muss die rechte Seite des Mitgliederzugangs . (Präzedenz 17) ein Bezeichner anstelle eines gruppierten Ausdrucks sein. Die linke Seite des Pfeils => (Präzedenz 2) muss eine Argumentenliste oder ein einzelner Bezeichner anstelle eines zufälligen Ausdrucks sein.
  3. Einige Operatoren haben bestimmte Operanden, die Ausdrücke akzeptieren, die weiter sind als jene, die von höheren Präzedenzoperatoren produziert werden. Zum Beispiel kann der klammerumfasste Ausdruck der Klammernotation [ … ] (Präzedenz 17) jeder Ausdruck sein, selbst durch Komma (Präzedenz 1) verbundene. Diese Operatoren wirken, als ob jener Operand "automatisch gruppiert" wäre. In diesem Fall werden wir die Assoziativität weglassen.
Präzedenz Assoziativität Individuelle Operatoren Anmerkungen
18: Gruppierung n/v Grouping
(x)
[1]
17: Zugriff und Aufruf links-nach-rechts Mitgliedszugriff
x.y
[2]
Optionales Chaining
x?.y
n/v Berechneter Mitgliedszugriff
x[y]
[3]
new mit Argumentenliste
new x(y)
[4]
Funktionsaufruf
x(y)
import(x)
16: new n/v new ohne Argumentenliste
new x
15: Postfix-Operatoren n/v Postfix-Inkrement
x++
[5]
Postfix-Dekrement
x--
14: Präfix-Operatoren n/v Präfix-Inkrement
++x
[6]
Präfix-Dekrement
--x
Logisches NOT
!x
Bitweises NOT
~x
Unäres Plus
+x
Unäre Negation
-x
typeof x
void x
delete x [7]
await x
13: Exponentialfunktion rechts-nach-links Exponentialfunktion
x ** y
[8]
12: Multiplikative Operatoren links-nach-rechts Multiplikation
x * y
Division
x / y
Rest
x % y
11: Additive Operatoren links-nach-rechts Addition
x + y
Subtraktion
x - y
10: Bitweise Verschiebung links-nach-rechts Linksverschiebung
x << y
Rechtsverschiebung
x >> y
Unsigned Rechtsverschiebung
x >>> y
9: Relationale Operatoren links-nach-rechts Kleiner als
x < y
Kleiner als oder gleich
x <= y
Größer als
x > y
Größer als oder gleich
x >= y
x in y
x instanceof y
8: Gleichheitsoperatoren links-nach-rechts Gleichheit
x == y
Ungleichheit
x != y
Strikte Gleichheit
x === y
Strikte Ungleichheit
x !== y
7: Bitweises UND links-nach-rechts Bitweises UND
x & y
6: Bitweises XOR links-nach-rechts Bitweises XOR
x ^ y
5: Bitweises ODER links-nach-rechts Bitweises ODER
x | y
4: Logisches UND links-nach-rechts Logisches UND
x && y
3: Logisches ODER, Nullish-Koaleszenz links-nach-rechts Logisches ODER
x || y
Nullish-Koaleszenz-operator
x ?? y
[9]
2: Zuweisung und Verschiedenes rechts-nach-links Zuweisung
x = y
[10]
Additionszuweisung
x += y
Subtraktionszuweisung
x -= y
Exponentialzuweisung
x **= y
Multiplikationszuweisung
x *= y
Divisionszuweisung
x /= y
Restzuweisung
x %= y
Linksverschiebungszuweisung
x <<= y
Rechtsverschiebungszuweisung
x >>= y
Unsigned Rechtsverschiebungszuweisung
x >>>= y
Bitweise UND-Zuweisung
x &= y
Bitweise XOR-Zuweisung
x ^= y
Bitweise ODER-Zuweisung
x |= y
Logische UND-Zuweisung
x &&= y
Logische ODER-Zuweisung
x ||= y
Nullish-Koaleszenz-Zuweisung
x ??= y
rechts-nach-links Bedingungsoperator (ternär)
x ? y : z
[11]
rechts-nach-links Pfeil
x => y
[12]
n/v yield x
yield* x
Spread
...x
[13]
1: Komma links-nach-rechts Kommaoperator
x, y

Anmerkungen:

  1. Der Operand kann jeder Ausdruck sein.
  2. Die "rechte Seite" muss ein Bezeichner sein.
  3. Die "rechte Seite" kann jeder Ausdruck sein.
  4. Die "rechte Seite" ist eine durch Komma getrennte Liste beliebiger Ausdrücke mit Präzedenz > 1 (d.h. keine Kommaausdrücke). Der Konstruktor eines new-Ausdrucks kann keine optionale Kette sein.
  5. Der Operand muss ein gültiges Zuweisungsziel sein (Bezeichner oder Memberzugriff). Seine Präzedenz bedeutet, dass new Foo++ (new Foo)++ (ein Syntaxfehler) und nicht new (Foo++) (ein TypeError: (Foo++) ist kein Konstruktor) ist.
  6. Der Operand muss ein gültiges Zuweisungsziel sein (Bezeichner oder Memberzugriff).
  7. Der Operand kann kein Bezeichner oder ein privates Element-Zugriff sein.
  8. Die linke Seite kann keine Präzedenz 14 haben.
  9. Die Operanden können nicht ein logisches ODER || oder ein logisches UND && Operator ohne Gruppierung sein.
  10. Die "linke Seite" muss ein gültiges Zuweisungsziel sein (Bezeichner oder Memberzugriff).
  11. Die Assoziativität bedeutet, dass die beiden Ausdrücke nach ? implizit gruppiert sind.
  12. Die "linke Seite" ist ein einzelner Bezeichner oder eine parenthesierte Parameterliste.
  13. Nur gültig innerhalb von Objektliteralen, Array-Literalen oder Argumentenlisten.

Die Präzedenz der Gruppen 17 und 16 kann etwas mehrdeutig sein. Hier sind einige Beispiele zur Klärung.

  • Optionales Chaining ist immer für seine jeweilige Syntax ohne Optionalität austauschbar (außer in einigen speziellen Fällen, in denen optionales Chaining verboten ist). Zum Beispiel akzeptiert jeder Ort, der a?.b akzeptiert, auch a.b und umgekehrt und ähnlich für a?.(), a(), etc.
  • Mitgliedsausdrücke und berechnete Mitgliedsausdrücke sind immer austauschbar.
  • Aufrufausdrücke und import()-Ausdrücke sind immer austauschbar.
  • Dies lässt vier Klassen von Ausdrücken: Mitgliedszugriff, new mit Argumenten, Funktionsaufruf und new ohne Argumente.
    • Die "linke Seite" eines Mitgliedszugriffs kann sein: ein Mitgliedszugriff (a.b.c), new mit Argumenten (new a().b) und Funktionsaufruf (a().b).
    • Die "linke Seite" von new mit Argumenten kann sein: ein Mitgliedszugriff (new a.b()) und new mit Argumenten (new new a()()).
    • Die "linke Seite" eines Funktionsaufrufs kann sein: ein Mitgliedszugriff (a.b()), new mit Argumenten (new a()()), und Funktionsaufruf (a()()).
    • Der Operand von new ohne Argumente kann sein: ein Mitgliedszugriff (new a.b), new mit Argumenten (new new a()), und new ohne Argumente (new new a).








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/Operator_precedence

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy