Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten.Erfahre mehr über dieses Experiment.
Operatorpräzedenz
Operatorpräzedenz bestimmt, wie Operatoren im Verhältnis zueinander geparst werden. Operatoren mit höherer Präzedenz werden zu den Operanden von Operatoren mit niedrigerer Präzedenz.
In diesem Artikel
Probieren Sie es aus
console.log(3 + 4 * 5); // 3 + 20// Expected output: 23console.log(4 * 3 ** 2); // 4 * 9// Expected output: 36let a;let b;console.log((a = b = 5));// Expected output: 5Präzedenz und Assoziativität
Betrachten Sie einen Ausdruck, der durch die untenstehende Darstellung beschreibbar ist, bei der sowohlOP1 als auchOP2 Platzhalter für Operatoren sind.
a OP1 b OP2 c
Die obenstehende Kombination hat zwei mögliche Interpretationen:
(a OP1 b) OP2 ca OP1 (b OP2 c)
Welche von ihnen die Sprache zu übernehmen entscheidet, hängt von der Identität vonOP1 undOP2 ab.
HabenOP1 undOP2 unterschiedliche Präzedenzstufen (siehe Tabelle unten), geht der Operator mit der höherenPräzedenz als erster vor, und Assoziativität spielt keine Rolle. Beachten Sie, wie Multiplikation eine höhere Präzedenz hat als Addition und zuerst ausgeführt wird, obwohl Addition im Code zuerst geschrieben wird.
console.log(3 + 10 * 2); // 23console.log(3 + (10 * 2)); // 23, because parentheses here are superfluousconsole.log((3 + 10) * 2); // 26, because the parentheses change the orderInnerhalb von Operatoren mit derselben Präzedenz gruppiert die Sprache diese nachAssoziativität.Linksassoziativität (von links nach rechts) bedeutet, dass es als(a OP1 b) OP2 c interpretiert wird, währendRechtsassoziativität (von rechts nach links) bedeutet, dass es alsa OP1 (b OP2 c) interpretiert wird. Zuweisungsoperatoren sind rechtsassoziativ, so dass Sie schreiben können:
a = b = 5; // same as writing a = (b = 5);mit dem erwarteten Ergebnis, dassa undb den Wert 5 erhalten. Dies liegt daran, dass der Zuweisungsoperator den zugewiesenen Wert zurückgibt. Zuerst wirdb auf 5 gesetzt. Dann wirda ebenfalls auf 5 gesetzt — der Rückgabewert vonb = 5, alias rechter Operand der Zuweisung.
Ein weiteres Beispiel ist der einzigartige Exponentialoperator, der rechtsassoziativ ist, während andere arithmetische Operatoren linksassoziativ sind.
const a = 4 ** 3 ** 2; // Same as 4 ** (3 ** 2); evaluates to 262144const b = 4 / 3 / 2; // Same as (4 / 3) / 2; evaluates to 0.6666...Operatoren werden zuerst nach Präzedenz und dann, für benachbarte Operatoren mit derselben Präzedenz, nach Assoziativität gruppiert. Werden Division und Exponentiation gemischt, kommt die Exponentiation immer vor der Division. Beispielsweise ergibt2 ** 3 / 3 ** 2 0,8888888888888888, weil es dasselbe ist wie(2 ** 3) / (3 ** 2).
Für präfixe unäre Operatoren nehmen wir folgendes Muster an:
OP1 a OP2 b
wobeiOP1 ein präfixer unärer Operator undOP2 ein binärer Operator ist. HatOP1 eine höhere Präzedenz alsOP2, wird es als(OP1 a) OP2 b gruppiert; andernfalls alsOP1 (a OP2 b).
const a = 1;const b = 2;typeof a + b; // Equivalent to (typeof a) + b; result is "number2"Befindet sich der unäre Operator auf dem zweiten Operanden:
a OP2 OP1 b
Dann muss der binäre OperatorOP2 eine niedrigere Präzedenz haben als der unäre OperatorOP1, damit es alsa OP2 (OP1 b) gruppiert wird. Beispielsweise ist Folgendes ungültig:
function* foo() { a + yield 1;}Da+ eine höhere Präzedenz hat alsyield, würde dies zu(a + yield) 1 werden — aber dayield einreserviertes Wort in Generatorfunktionen ist, wäre dies ein Syntaxfehler. Glücklicherweise haben die meisten unären Operatoren eine höhere Präzedenz als binäre Operatoren und leiden nicht unter diesem Stolperstein.
Haben wir zwei präfixe unäre Operatoren:
OP1 OP2 a
Dann muss der unäre Operator, der näher am Operanden liegt,OP2, eine höhere Präzedenz haben alsOP1, damit es alsOP1 (OP2 a) gruppiert wird. Es ist möglich, es anders herum zu bekommen und bei(OP1 OP2) a zu landen:
async function* foo() { await yield 1;}Daawait eine höhere Präzedenz alsyield hat, würde dies zu(await yield) 1, was bedeutet, auf einen Bezeichner namensyield zu warten, was ein Syntaxfehler ist. Ähnlich, wenn Sienew !A; haben, da! eine niedrigere Präzedenz hat alsnew, würde dies zu(new !) A, was offensichtlich ungültig ist. (Dieser Code scheint ohnehin unsinnig zu sein, da!A immer einen booleschen Wert liefert, keine Konstrukturfunktion.)
Für postfixe unäre Operatoren (nämlich++ und--) gelten dieselben Regeln. Glücklicherweise haben beide Operatoren eine höhere Präzedenz als jeder binäre Operator, so dass die Gruppierung immer so ist, wie Sie es erwarten würden. Da++ zudem einenWert und keinenVerweis ergibt, können Sie auch keine mehrfachen Inkremente zusammenketten, wie Sie es in C tun könnten.
let a = 1;a++++; // SyntaxError: Invalid left-hand side in postfix operation.Die Operatorpräzedenz wirdrekursiv behandelt. Beispielsweise betrachten Sie diesen Ausdruck:
1 + 2 ** 3 * 4 / 5 >> 6Zuerst gruppieren wir Operatoren mit unterschiedlicher Präzedenz nach absteigenden Präzedenzstufen.
- Der
**-Operator hat die höchste Präzedenz, daher wird er zuerst gruppiert. - In der Nähe des
**-Ausdrucks hat es*rechts und+links.*hat eine höhere Präzedenz, daher wird es zuerst gruppiert.*und/haben die gleiche Präzedenz, also gruppieren wir sie vorerst zusammen. - In der Nähe des in 2 gruppierten
*//-Ausdrucks wird, da+eine höhere Präzedenz als>>hat, ersteres gruppiert.
(1 + ( (2 ** 3) * 4 / 5) ) >> 6// │ │ └─ 1. ─┘ │ │// │ └────── 2. ───────┘ │// └────────── 3. ──────────┘Innerhalb der*//-Gruppe, da sie beide linksassoziativ sind, würde der linke Operand gruppiert.
(1 + ( ( (2 ** 3) * 4 ) / 5) ) >> 6// │ │ │ └─ 1. ─┘ │ │ │// │ └─│─────── 2. ───│────┘ │// └──────│───── 3. ─────│──────┘// └───── 4. ─────┘Beachten Sie, dass Operatorpräzedenz und Assoziativität nur die Reihenfolge der Bewertung vonOperatoren (die implizite Gruppierung) beeinflussen, nicht aber die Reihenfolge der Bewertung vonOperanden. Die Operanden werden immer von links nach rechts ausgewertet. Die Ausdrücke mit höherer Präzedenz werden immer zuerst ausgewertet, und ihre Ergebnisse werden dann entsprechend der Reihenfolge der Operatorpräzedenz zusammengesetzt.
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 ** doesconsole.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 operandconsole.log(echo("left", 4) / echo("middle", 3) ** echo("right", 2));// Evaluating the left side// Evaluating the middle side// Evaluating the right side// 0.4444444444444444Wenn Sie mit binären Bäumen vertraut sind, denken Sie daran wie an einepost-order Traversierung.
/ ┌────────┴────────┐echo("left", 4) ** ┌────────┴────────┐ echo("middle", 3) echo("right", 2)Nachdem alle Operatoren ordnungsgemäß gruppiert wurden, würden die binären Operatoren einen binären Baum bilden. Die Auswertung beginnt mit 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 Operatoren mit höherer Präzedenz bestehen kann (wie ein Aufrufausdruckecho("left", 4)). Nachdem der linke Operand ausgewertet wurde, wird der rechte Operand auf die gleiche Weise ausgewertet. Daher würden alle Blattknoten — dieecho()-Aufrufe — von links nach rechts besucht werden, unabhängig von der Präzedenz der sie verbindenden Operatoren.
Short-Circuiting
Im vorherigen Abschnitt sagten wir "Die Ausdrücke mit höherer Präzedenz werden immer zuerst ausgewertet" — dies ist im Allgemeinen wahr, muss jedoch mit dem Hinweis aufShort-Circuiting ergänzt werden, bei dem ein Operand möglicherweise überhaupt nicht ausgewertet wird.
Short-Circuiting ist ein Fachbegriff für bedingte Auswertung. Beispielsweise wird im Ausdrucka && (b + c), wennafalsy ist, der Unterausdruck(b + c) nicht einmal ausgewertet, auch wenn er gruppiert ist und somit eine höhere Präzedenz als&& hat. Wir könnten sagen, dass der logische UND-Operator (&&) "verkürzt" ist. Neben dem logischen UND gehören zu den verkürzten Operatoren auch logisches ODER (||), Nullish-Koaleszenz (??) und optionales Chaining (?.).
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`Beim Auswerten eines verkürzten 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 von Short-Circuiting ist in diese Operatoren eingebaut. Bei anderen Operatoren würdenimmer beide Operanden ausgewertet, unabhängig davon, ob das tatsächlich nützlich ist — zum Beispiel wirdNaN * foo() immerfoo aufrufen, selbst wenn das Ergebnis niemals etwas anderes alsNaN wäre.
Das vorherige Modell einer post-order Traversierung bleibt bestehen. Nachdem jedoch der linke Teilbaum eines verkürzten Operators besucht wurde, entscheidet die Sprache, ob der rechte Operand ausgewertet werden muss. Wenn nicht (zum Beispiel, weil der linke Operand von|| bereits wahrheitsgemäß ist), wird das Ergebnis direkt zurückgegeben, ohne den rechten Teilbaum zu besuchen.
Betrachten Sie diesen Fall:
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// trueNurC() wird ausgewertet, obwohl&& eine höhere Präzedenz hat. Dies bedeutet nicht, dass|| in diesem Fall eine höhere Präzedenz hat — genauweil(B() && A()) eine höhere Präzedenz hat, wird es als Ganzes vernachlässigt. Wenn es umgestellt wird als:
console.log(A() && B() || C());// Logs:// called A// called C// trueDann würde der Short-Circuiting-Effekt von&& nur verhindern, dassB() ausgewertet wird, aber daA() && B() als Ganzesfalse ist, würdeC() dennoch ausgewertet.
Beachten Sie jedoch, dass Short-Circuiting das endgültige Bewertungsergebnis nicht ändert. Es beeinflusst nur die Bewertung derOperanden, nicht, wieOperatoren gruppiert werden — wenn die Bewertung der Operanden keine Seiteneffekte hat (zum Beispiel Konsolenausgaben, Zuweisungen zu Variablen, das Werfen eines Fehlers), wäre Short-Circuiting überhaupt nicht beobachtbar.
Auch die Zuweisungsvariante dieser Operatoren (&&=,||=,??=) sind verkürzt. Sie sind so verkürzt, dass die Zuweisung überhaupt nicht erfolgt.
Tabelle
Die folgende Tabelle listet die Operatoren in der Reihenfolge von der höchsten Präzedenz (18) bis zur niedrigsten Präzedenz (1) auf.
Einige allgemeine Anmerkungen zur Tabelle:
- Nicht alle hier enthaltenen Syntaxen sind im strengen Sinne "Operatoren". Zum Beispiel werden Spread
...und Pfeil=>typischerweise nicht als Operatoren angesehen. Wir haben sie jedoch dennoch aufgenommen, um zu zeigen, wie fest sie im Vergleich zu anderen Operatoren/Ausdrücken binden. - Einige Operatoren haben bestimmte Operanden, die Ausdrücke erfordern, die enger sind als die, die von Operatoren mit höherer Präzedenz produziert werden. Zum Beispiel muss die rechte Seite des Memberzugriffs
.(Präzedenz 17) ein Bezeichner anstelle eines gruppierten Ausdrucks sein. Die linke Seite des Pfeils=>(Präzedenz 2) muss eine Argumentliste oder ein einzelner Bezeichner anstelle eines zufälligen Ausdrucks sein. - Einige Operatoren haben bestimmte Operanden, die Ausdrücke akzeptieren, die weiter sind als die, die von Operatoren mit höherer Präzedenz produziert werden. Zum Beispiel kann der klammernverschlossene Ausdruck der Klammernotation
[ … ](Präzedenz 17) jeder Ausdruck sein, selbst durch Komma (Präzedenz 1) verbundene. Diese Operatoren wirken so, als wäre dieser Operand "automatisch gruppiert". In diesem Fall werden wir die Assoziativität weglassen.
| Präzedenz | Assoziativität | Einzelne Operatoren | Anmerkungen || ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------- | ---------- || 18: Gruppierung | n/a |Grouping(x) | [1] || rowspan="6" 17: Zugriff und Aufruf | rowspan="2" left-to-right |Member accessx.y | rowspan="2" [2] || | |Optional chainingx?.y || | rowspan="4" n/a |Computed member accessx[y] | [3] || | |new mit Argumentlistenew x(y) | rowspan="3" [4] || | |Funktionsaufrufx(y) || | |import(x) || 16: new | n/a |new ohne Argumentlistenew x || rowspan="2" 15: postfix) Operatoren | rowspan="2" n/a |Postfix incrementx++ | rowspan="2" [5] || | |Postfix decrementx-- || rowspan="10" 14: Präfix) Operatoren | rowspan="10" n/a |Prefix increment++x | rowspan="2" [6] || | |Prefix decrement--x || | |Logical NOT!x || | |Bitwise NOT~x || | |Unary plus+x || | |Unary negation-x || | |typeof x || | |void x || | |delete x | [7] || | |await x || 13: Exponential | right-to-left |Exponentiationx ** y | [8] || rowspan="3" 12: Multiplikationsoperatoren | rowspan="3" left-to-right |Multiplicationx * y || | |Divisionx / y || | |Remainderx % y || rowspan="2" 11: Additionsoperatoren | rowspan="2" left-to-right |Additionx + y || | |Subtractionx - y || rowspan="3" 10: Bitweise Verschiebung | rowspan="3" left-to-right |Left shiftx << y || | |Right shiftx >> y || | |Unsigned right shiftx >>> y || rowspan="6" 9: Relationale Operatoren | rowspan="6" left-to-right |Less thanx < y || | |Less than or equalx <= y || | |Greater thanx > y || | |Greater than or equalx >= y || |x in y || |x instanceof y || rowspan="4" 8: Gleichheitsoperatoren | rowspan="4" left-to-right |Equalityx == y || | |Inequalityx != y || | |Strict equalityx === y || | |Strict inequalityx !== y || 7: Bitweise UND | left-to-right |Bitwise ANDx & y || 6: Bitweise XOR | left-to-right |Bitwise XORx ^ y || 5: Bitweise ODER | left-to-right |Bitwise ORx | y || 4: Logisches UND | left-to-right |Logical ANDx && y || rowspan="2" 3: Logisches ODER, Nullish-Koaleszenz | rowspan="2" left-to-right |Logical ORx | | y || | |Nullish coalescing operatorx ?? y | [9] || rowspan="21" 2: Zuweisung und Verschiedenes | rowspan="16" right-to-left |Assignmentx = y | rowspan="16" [10] || | |Addition assignmentx += y || | |Subtraction assignmentx -= y || | |Exponentiation assignmentx **= y || | |Multiplication assignmentx *= y || | |Division assignmentx /= y || | |Remainder assignmentx %= y || | |Left shift assignmentx <<= y || | |Right shift assignmentx >>= y || | |Unsigned right shift assignmentx >>>= y || | |Bitwise AND assignmentx &= y || | |Bitwise XOR assignmentx ^= y || | |Bitwise OR assignmentx | = y || | |Logical AND assignmentx &&= y || | |Logical OR assignmentx | | = y || | |Nullish coalescing assignmentx ??= y || right-to-left |Bedingter (ternärer) Operatorx ? y : z | [11] || right-to-left |Pfeilx => y | [12] || rowspan="3" n/a |yield x || |yield* x || |Spread...x | [13] || 1: Komma | left-to-right |Comma operatorx, y |
Anmerkungen:
- Der Operand kann jeder Ausdruck sein.
- Die "rechte Seite" muss ein Bezeichner sein.
- Die "rechte Seite" kann jeder Ausdruck sein.
- Die "rechte Seite" ist eine kommagetrennte Liste von Ausdrücken mit einer Präzedenz > 1 (d.h. keine Komma-Ausdrücke). Der Konstruktor eines
new-Ausdrucks kann keine optionale Kette sein. - Der Operand muss ein gültiges Zuweisungsziel (Bezeichner oder Eigenschaftszugriff) sein. Seine Präzedenz bedeutet, dass
new Foo++new (Foo++)(ein Syntaxfehler) und nichtnew (Foo++)(ein TypeError: (Foo++) ist kein Konstruktor) ist. - Der Operand muss ein gültiges Zuweisungsziel (Bezeichner oder Eigenschaftszugriff) sein.
- Der Operand kann kein Bezeichner oder einprivater Zugriff sein.
- Die linke Seite kann keine Präzedenz von 14 haben.
- Die Operanden können kein logisches ODER
||oder logisches UND&&Operator ohne Gruppierung sein. - Die "linke Seite" muss ein gültiges Zuweisungsziel (Bezeichner oder Eigenschaftszugriff) sein.
- Die Assoziativität bedeutet, dass die beiden Ausdrücke nach
?implizit gruppiert sind. - Die "linke Seite" ist ein einzelner Bezeichner oder eine eingeklammertes Parameterliste.
- Nur gültig in Objektliteralen, Array-Literalen oder Argumentlisten.
Die Präzedenz der Gruppen 17 und 16 kann etwas zweideutig sein. Hier sind einige Beispiele zur Klärung:
- Optionales Chaining ist immer durch seine jeweilige Syntax ohne Optionalität austauschbar (abgesehen von einigen speziellen Fällen, in denen optionales Chaining verboten ist). Zum Beispiel akzeptiert jede Stelle, die
a?.bakzeptiert, aucha.bund umgekehrt, und ähnliches füra?.(),a(), etc. - Memberausdrucke und berechnete Memberausdrucke sind immer austauschbar.
- Funktionsaufrufe und
import()-Ausdrucke sind immer austauschbar. - Dies lässt vier Klassen von Ausdrücken: Memberzugriff,
newmit Argumenten, Funktionsaufruf undnewohne Argumente.- Die "linke Seite" eines Memberzugriffs kann sein: ein Memberzugriff (
a.b.c),newmit Argumenten (new a().b), und Funktionsaufruf (a().b). - Die "linke Seite" von
newmit Argumenten kann sein: ein Memberzugriff (new a.b()) undnewmit Argumenten (new new a()()). - Die "linke Seite" eines Funktionsaufrufs kann sein: ein Memberzugriff (
a.b()),newmit Argumenten (new a()()), und Funktionsaufruf (a()()). - Der Operand von
newohne Argumente kann sein: ein Memberzugriff (new a.b),newmit Argumenten (new new a()), undnewohne Argumente (new new a).
- Die "linke Seite" eines Memberzugriffs kann sein: ein Memberzugriff (