Content-Length: 295118 | 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 zu 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 Operatoren sind.

a OP1 b OP2 c

Das obige Diagramm hat zwei mögliche Interpretationen:

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

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

Wenn OP1 und OP2 unterschiedliche Präzedenzstufen haben (siehe Tabelle unten), wird der Operator mit der höheren Präzedenz zuerst ausgeführt, und die Assoziativität spielt keine Rolle. Beachten Sie, dass die Multiplikation eine höhere Präzedenz als die Addition hat und zuerst ausgeführt wird, auch wenn die Addition zuerst im Code 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 werden sie von der Sprache nach Assoziativität gruppiert. Linksassoziativität (von links nach rechts) bedeutet, dass sie interpretiert wird als (a OP1 b) OP2 c, während Rechtsassoziativität (von rechts nach links) bedeutet, dass sie interpretiert wird als a OP1 (b OP2 c). 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.

Als weiteres Beispiel hat der einzigartige Exponentialoperator eine Rechtsassoziativität, während andere arithmetische Operatoren eine Linksassoziativität haben.

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 gruppiert, und dann für benachbarte Operatoren mit derselben Präzedenz nach Assoziativität. Beim Mischen von Division und Exponentialrechnung kommt die Exponentialrechnung immer vor der Division. Zum Beispiel ergibt 2 ** 3 / 3 ** 2 0.8888888888888888, weil es dasselbe ist wie (2 ** 3) / (3 ** 2).

Für unäre Präfixoperatoren, nehmen wir folgendes Muster an:

OP1 a OP2 b

wo OP1 ein unärer Präfixoperator 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"

Wenn der unäre Operator auf dem zweiten Operanden liegt:

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 — 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 unäre Präfixoperatoren:

OP1 OP2 a

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

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

Weil await eine höhere Präzedenz als yield, wird dies zu (await yield) 1, was bedeutet, dass auf einen Identifikator namens yield gewartet wird, und ein Syntaxfehler. Ähnlich, wenn Sie new !A; haben, weil ! eine niedrigere Präzedenz als new hat, wird dies zu (new !) A, was offensichtlich ungültig ist. (Dieser Code sieht sowieso unsinnig aus, da !A immer ein Boolean, keine Konstruktorfunktion ergibt.)

Für unäre Postfixoperatoren (nämlich ++ und --) gelten die gleichen Regeln. Zum Glück haben beide Operatoren eine höhere Präzedenz als jeder binäre Operator, sodass die Gruppierung immer so ist, wie man es erwarten würde. Außerdem, da ++ auf einen Wert und nicht auf eine Referenz evaluiert wird, können Sie nicht mehrere Inkremente miteinander verkettet schreiben.

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 unterschiedlicher Präzedenz nach absteigenden Präzedenzstufen.

  1. Der **-Operator hat die höchste Präzedenz, daher wird er zuerst gruppiert.
  2. Betrachtet man den **-Ausdruck, hat er * rechts und + links. * hat eine höhere Präzedenz, wird also zuerst gruppiert. * und / haben dieselbe Präzedenz, daher gruppieren wir sie zunächst.
  3. Betrachtet man den in 2 gruppierten *//-Ausdruck, weil + eine höhere Präzedenz als >> hat, wird zuerst die +-Gruppe gebildet.
js
   (1 + ( (2 ** 3) * 4 / 5) ) >> 6
// │    │ └─ 1. ─┘        │ │
// │    └────── 2. ───────┘ │
// └────────── 3. ──────────┘

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

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

Beachten Sie, dass Operatorpräzedenz und Assoziativität nur die Reihenfolge der Auswertung von Operatoren beeinflussen (die implizite Gruppierung), nicht aber die Auswertungsreihenfolge der Operanden. Die Operanden werden immer von links nach rechts ausgewertet. Die höherstufigen Präzedenz-Ausdrücke werden immer zuerst evaluiert, und ihre Ergebnisse werden dann gemäß 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ären Bäumen vertraut sind, denken Sie daran wie bei einer Post-Order-Traversierung.

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

Nachdem alle Operatoren ordnungsgemäß gruppiert sind, bilden die binären Operatoren einen Binärbaum. Die Auswertung beginnt mit der äußersten Gruppe — die der Operator mit der niedrigsten Präzedenz ist (in diesem Fall /). Der linke Operand dieses Operators wird zuerst ausgewertet, der möglicherweise aus höherstufigen Präzedenz-Operatoren besteht (wie ein Aufrufausdruck in echo("left", 4)). Nachdem der linke Operand ausgewertet wurde, wird der rechte Operand auf die gleiche 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.

Kurzschluss

Im vorherigen Abschnitt sagten wir: "die höherstufigen Präzedenz-Ausdrücke werden immer zuerst ausgewertet" — das ist im Allgemeinen wahr, muss jedoch mit der Anerkennung des Kurzschlusses ergänzt werden, bei dem ein Operand möglicherweise überhaupt nicht ausgewertet wird.

Kurzschluss ist Fachjargon für bedingte Auswertung. Zum Beispiel, im Ausdruck a && (b + c), wenn a falsy ist, wird der Teilausdruck (b + c) gar nicht ausgewertet, selbst wenn er gruppiert ist und daher eine höhere Präzedenz als && hat. Man könnte sagen, dass der logische Und-Operator (&&) "kurzgeschlossen" ist. Zusammen mit logischem Und sind andere kurzgeschlossene Operatoren logisches Oder (||), "nullish coalescing" (??) und optionales Verkettung (?.).

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 immer der linke Operand 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 diesen Operatoren eingebaut. Andere Operatoren würden immer beide Operanden auswerten, unabhängig davon, ob dies tatsächlich nützlich ist — zum Beispiel wird NaN * foo() immer foo aufrufen, selbst wenn das Ergebnis nie etwas anderes als NaN wäre.

Das vorherige Modell einer Post-Order-Traversierung bleibt bestehen. Allerdings entscheidet die Sprache, nachdem der linke Unterbaum eines kurzgeschlossenen Operators besucht wurde, 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 Unterbaum 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 dass && eine höhere Präzedenz hat. Das 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 dieser Ausdruck als Ganzes vernachlässigt wird. Wenn es umgestellt wird als:

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

Dann würde der Kurzschluss-Effekt von && lediglich verhindern, dass B() ausgewertet wird, aber da A() && B() als Ganzes false ist, würde C() trotzdem ausgewertet werden.

Beachten Sie jedoch, dass der Kurzschluss das Endergebnis der Auswertung nicht ändert. Es beeinflusst nur die Auswertung der Operanden, nicht wie Operatoren gruppiert werden — wenn die Auswertung der Operanden keine Nebeneffekte hat (zum Beispiel Protokollierung in der Konsole, Variablenzuweisungen, Fehler auslösen), wäre der Kurzschluss überhaupt nicht bemerkbar.

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 der höchsten Präzedenz (18) zur niedrigsten Präzedenz (1) auf.

Einige allgemeine Anmerkungen zur Tabelle:

  1. Nicht alle hier enthaltenen Syntaxelemente sind im strengen Sinne "Operatoren". Zum Beispiel werden Spread ... und Arrow => normalerweise nicht als Operatoren betrachtet. Wir haben sie jedoch einbezogen, um zu zeigen, wie eng sie im Vergleich zu anderen Operatoren/ Ausdrücken binden.
  2. Einige Operatoren haben bestimmte Operanden, die Ausdrücke erfordern, die enger sind als die von höherstufigen Präzedenz-Operatoren erzeugten. Zum Beispiel muss die rechte Seite des .-Zugriffsoperators (Präzedenz 17) ein Bezeichner anstelle eines gruppierten Ausdrucks sein. Die linke Seite des Arrow => (Präzedenz 2) muss eine Argumenteliste oder ein einzelner Bezeichner statt eines zufälligen Ausdrucks sein.
  3. Einige Operatoren haben bestimmte Operanden, die Ausdrücke akzeptieren, die breiter sind als die von höherstufigen Präzedenz-Operatoren produzierten. Zum Beispiel kann der klammergeschützte Ausdruck der Klammernotation [ … ] (Präzedenz 17) jeder Ausdruck sein, sogar durch Kommas (Präzedenz 1) verbundene. Diese Operatoren verhalten sich, als ob der Operand "automatisch gruppiert" wäre. In diesem Fall werden wir die Assoziativität weglassen.
Präzedenz Assoziativität Einzelne Operatoren Bemerkungen
18: Gruppierung k.A. Gruppierung
(x)
[1]
17: Zugriff und Aufruf links-nach-rechts Mitgliedszugriff
x.y
[2]
Optionale Verkettung
x?.y
k.A. Berechneter Mitgliedszugriff
x[y]
[3]
new mit Argumenteliste
new x(y)
[4]
Funktionsaufruf
x(y)
import(x)
16: new k.A. new ohne Argumenteliste
new x
15: Postfix-Operatoren k.A. Postfix-Inkrement
x++
[5]
Postfix-Dekrement
x--
14: Präfix-Operatoren k.A. Präfix-Inkrement
++x
[6]
Präfix-Dekrement
--x
Logisches NICHT
!x
Bitweises NICHT
~x
Unäres Plus
+x
Unäre Negation
-x
typeof x
void x
delete x [7]
await x
13: Exponentiation rechts-nach-links Exponentiation
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: Bitweises Verschieben links-nach-rechts Linksschieben
x << y
Rechtsschieben
x >> y
Ungar. Rechtsschieben
x >>> y
9: Relationale Operatoren links-nach-rechts Kleiner als
x < y
Kleiner gleich
x <= y
Größer als
x > y
Größer gleich
x >= y
x in y
x instanceof y
8: Gleichheitsoperatoren links-nach-rechts Gleichheit
x == y
Ungleichheit
x != y
Strenge Gleichheit
x === y
Strenge 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 coalescing links-nach-rechts Logisches ODER
x || y
Nullish coalescing Operator
x ?? y
[9]
2: Zuweisung und Sonstiges rechts-nach-links Zuweisung
x = y
[10]
Additionszuweisung
x += y
Subtraktionszuweisung
x -= y
Exponentiationszuweisung
x **= y
Multiplikationszuweisung
x *= y
Divisionszuweisung
x /= y
Restzuweisung
x %= y
Linksverschiebungszuweisung
x <<= y
Rechtsverschiebungszuweisung
x >>= y
Ungar. Rechtsverschiebungszuweisung
x >>>= y
Bitweises UND-Zuweisung
x &= y
Bitweises XOR-Zuweisung
x ^= y
Bitweises ODER-Zuweisung
x |= y
Logisches UND-Zuweisung
x &&= y
Logisches ODER-Zuweisung
x ||= y
Nullish coalescing Zuweisung
x ??= y
rechts-nach-links Bedingungsoperator (ternär)
x ? y : z
[11]
rechts-nach-links Arrow
x => y
[12]
k.A. yield x
yield* x
Spread
...x
[13]
1: Komma links-nach-rechts KommOperator
x, y

Bemerkungen:

  1. Der Operand kann jeder Ausdruck sein.
  2. Die "rechte Seite" muss ein Identifier sein.
  3. Die "rechte Seite" kann jeder Ausdruck sein.
  4. Die "rechte Seite" ist eine kommagetrennte Liste von beliebigen Ausdrücken mit einer Präzedenz > 1 (d.h. keine Komma-Ausdrücke). Der Konstruktor eines new-Ausdrucks kann nicht eine optionale Verkettung sein.
  5. Der Operand muss ein gültiges Zuweisungsziel (Identifier oder Eigenschaftszugriff) sein. Seine Präzedenz bedeutet new Foo++ ist (new Foo)++ (ein Syntaxfehler) und nicht new (Foo++) (ein TypeError: (Foo++) ist kein Konstruktor).
  6. Der Operand muss ein gültiges Zuweisungsziel (Identifier oder Eigenschaftszugriff) sein.
  7. Der Operand kann kein Identifier oder ein privater Element zugriff sein.
  8. Die linke Seite kann keine Präzedenz 14 haben.
  9. Die Operanden können nicht ein logisches ODER || oder logisches UND &&-Operator ohne Gruppierung sein.
  10. Die "linke Seite" muss ein gültiges Zuweisungsziel (Identifier oder Eigenschaftszugriff) sein.
  11. Die Assoziativität bedeutet, dass die zwei Ausdrücke nach dem ? implizit gruppiert sind.
  12. Die "linke Seite" ist ein einzelnes Identifier oder eine geklammerte Parameterliste.
  13. Nur gültig innerhalb von Objekt-Literalen, Array-Literalen oder Argumentenlisten.

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

  • Optionale Verkettung kann immer durch ihre entsprechende Syntax ohne Optionalität ersetzt werden (mit Ausnahme einiger besonderer Fälle, in denen optionale Verkettung verboten ist). Beispielsweise akzeptiert jede Stelle, die a?.b akzeptiert, auch a.b und umgekehrt, und ähnlich für a?.(), a(), etc.
  • Mitgliedsausdrücke und berechnete Mitgliedsausdrücke sind immer gegeneinander austauschbar.
  • Aufrufs- und import()-Ausdrücke sind immer gegeneinander austauschbar.
  • Dadurch bleiben vier Klassen von Ausdrücken übrig: 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