Operator precedence
Приоритет операторов определяет, как операторы анализируются относительно друг друга. Операторы с более высоким приоритетом становятся операндами операторов с более низким приоритетом.
Try it
Приоритет и ассоциативность
Рассмотрим выражение, описываемое приведенным ниже представлением, где и OP1 , и OP2 заполняют пробелы для операторов OPerator.
Приведенная выше комбинация имеет два возможных толкования:
(a OP1 b) OP2 c a OP1 (b OP2 c)
Какой из них решит принять язык, зависит от идентичности OP1 и OP2 .
Если OP1 и OP2 имеют разные уровни приоритета (см. таблицу ниже), первым идет оператор с более высоким приоритетом , и ассоциативность не имеет значения. Обратите внимание, что умножение имеет более высокий приоритет, чем сложение, и выполняется первым, несмотря на то, что сложение записывается первым в коде.
console.log(3 + 10 * 2); // журналы 23 console.log(3 + (10 * 2)); // регистрирует 23, потому что круглые скобки здесь излишни console.log((3 + 10) * 2); // регистрирует 26, потому что скобки меняют порядок
Внутри операторов одного приоритета язык группирует их по ассоциативности . Левая ассоциативность (слева направо) означает, что она интерпретируется как (a OP1 b) OP2 c , а правая ассоциативность (справа налево) означает, что она интерпретируется как a OP1 (b OP2 c) . Операторы присваивания правоассоциативны, поэтому вы можете написать:
a = b = 5; // то же самое, что и запись a = (b = 5);
с ожидаемым результатом, что a и b получат значение 5. Это связано с тем, что оператор присваивания возвращает присваиваемое значение. Во- первых, b устанавливается равным 5. Затем a также устанавливается равным 5 — возвращаемое значение b = 5 , также известное как правый операнд присваивания.
В качестве другого примера,единственный оператор экспоненции имеет правую ассоциативность,в то время как другие арифметические операторы имеют левую ассоциативность.
const a = 4 ** 3 ** 2; //То же самое,что 4 **(3 **2);оценивается в 262144 const b = 4 / 3 / 2; //То же самое,что (4/3)/2;оценивается в 0,6666.
Операторы сначала группируются по приоритету, а затем для смежных операторов, имеющих одинаковый приоритет, по ассоциативности. Таким образом, при смешивании деления и возведения в степень возведение в степень всегда предшествует делению. Например, 2 ** 3 / 3 ** 2 приводит к 0,88888888888888888, потому что это то же самое, что и (2 ** 3) / (3 ** 2) .
Для префиксных унарных операторов предположим,что у нас есть следующая схема:
где OP1 — префиксный унарный оператор, а OP2 — бинарный оператор. Если OP1 имеет более высокий приоритет, чем OP2 , то он будет сгруппирован как (OP1 a) OP2 b ; в противном случае это был бы OP1 (a OP2 b) .
const a = 1; const b = 2; typeof a + b; //Эквивалентно (typeof a)+b;результат-"число2".
Если унарный оператор находится на втором операнде:
Тогда бинарный оператор OP2 должен иметь более низкий приоритет, чем унарный оператор OP1 , чтобы его можно было сгруппировать как a OP2 (OP1 b) . Например, следующее неверно:
Поскольку + имеет более высокий приоритет, чем yield , это станет (a + yield) 1 , но поскольку yield является зарезервированным словом в функциях-генераторах, это будет синтаксической ошибкой. К счастью, большинство унарных операций имеют более высокий приоритет, чем бинарные, и не страдают от этой ловушки.
Если у нас есть два префиксных унарных оператора:
Тогда унарный оператор, расположенный ближе к операнду, OP2 , должен иметь более высокий приоритет, чем OP1 , чтобы его можно было сгруппировать как OP1 (OP2 a) . Можно сделать по-другому и получить (OP1 OP2) a :
async function* foo( ) < await yield 1; >
Поскольку await имеет более высокий приоритет, чем yield , это будет (await yield) 1 , которое ожидает идентификатора yield и синтаксической ошибки. Точно так же, если у вас есть new !A; , потому что ! имеет более низкий приоритет, чем new , это станет (new !) A , что явно недопустимо. (В любом случае этот код выглядит бессмысленным, так как !A всегда создает логическое значение, а не функцию-конструктор.)
Для постфиксных унарных операторов (а именно, ++ и — ) применяются те же правила. К счастью, оба оператора имеют более высокий приоритет, чем любой бинарный оператор, поэтому группировка всегда соответствует ожидаемой. Более того, поскольку ++ оценивается как значение , а не как ссылка , вы также не можете объединить несколько приращений, как вы можете сделать в C.
let a = 1; a++++; //SyntaxError:Неверная левая часть в операции postfix.
Приоритет оператора будет обрабатываться рекурсивно . Например, рассмотрим это выражение:
Во-первых,мы группируем операторы с разным старшинством по убыванию уровня старшинства.
- Оператор ** имеет наивысший приоритет, поэтому он группируется первым.
- Глядя на выражение ** ,оно имеет * справа и + справа. * имеет более высокий приоритет, поэтому он группируется первым. * и / имеют одинаковый приоритет, поэтому мы пока сгруппируем их вместе.
- Глядя на выражение * / / , сгруппированное в 2, поскольку + имеет более высокий приоритет, чем >> , первое сгруппировано.
(1 + ( (2 ** 3) * 4 / 5) ) >> 6 // │ │ └─ 1. ─┘ │ │ // │ └────── 2. ───────┘ │ // └────────── 3. ──────────┘
В группе * // / поскольку они оба левоассоциативны, левый операнд будет сгруппирован.
(1 + ( ( (2 ** 3) * 4 ) / 5) ) >> 6 // │ │ │ └─ 1. ─┘ │ │ │ // │ └─│─────── 2. ───│────┘ │ // └──────│───── 3. ─────│──────┘ // └───── 4. ─────┘
Обратите внимание, что приоритет операций и ассоциативность влияют только на порядок оценки операторов (неявная группировка), но не на порядок оценки операндов . Операнды всегда оцениваются слева направо. Выражения с более высоким приоритетом всегда оцениваются первыми, а их результаты затем составляются в соответствии с порядком приоритета операторов.
function echo(name, num) < console.log(`Evaluating the $ side`); return num; > //Оператор экспоненциализации (**)является право-ассоциативным, //но все они вызывают выражения (echo()),которые имеют более высокий приоритет, //будет оценен до того,как это сделает ** console.log(echo("left", 4) ** echo("middle", 3) ** echo("right", 2)); //Оценка левой части //Оценка средней стороны //Оценка правой части // 262144 //Оператор экспоненциализации (**)имеет более высокий приоритет,чем деление (/), //но оценка всегда начинается с левого операнда console.log(echo("left", 4) / echo("middle", 3) ** echo("right", 2)); //Оценка левой части //Оценка средней стороны //Оценка правой части // 0.4444444444444444
Если вы знакомы с бинарными деревьями, подумайте об этом как об обратном обходе .
/ ┌────────┴────────┐ echo("left", 4) ** ┌────────┴────────┐ echo("middle", 3) echo("right", 2)
После того, как все операторы будут правильно сгруппированы, бинарные операторы образуют бинарное дерево. Вычисление начинается с самой внешней группы — оператора с наименьшим приоритетом ( в данном случае / ).Сначала оценивается левый операнд этого оператора, который может состоять из операторов с более высоким приоритетом (например, выражение вызова echo(«left», 4) ). После вычисления левого операнда таким же образом вычисляется правый операнд. Следовательно, все конечные узлы — вызовы echo() — будут посещаться слева направо, независимо от приоритета присоединяющихся к ним операторов.
Short-circuiting
В предыдущем разделе мы сказали, что «выражения с более высоким приоритетом всегда оцениваются первыми» — это, как правило, верно, но это должно быть изменено с подтверждением короткого замыкания , и в этом случае операнд может вообще не оцениваться.
Короткое замыкание — это жаргон для условной оценки. Например, в выражении a && (b + c) , если a равно false , то подвыражение (b + c) даже не будет оцениваться, даже если оно сгруппировано и, следовательно, имеет более высокий приоритет, чем && . Можно сказать, что логический оператор И ( && ) «замкнут». Наряду с логическим И другие сокращенные операторы включают логическое ИЛИ ( || ), нулевое объединение ( ?? ) и необязательное объединение в цепочку ( ?. ).
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`
При оценке оператора с замыканием всегда оценивается левый операнд.Правый операнд оценивается только в том случае,если левый операнд не может определить результат операции.
Примечание. В этих операторах заложено поведение короткого замыкания. Другие операторы всегда будут оценивать оба операнда, независимо от того, действительно ли это полезно — например, NaN * foo() всегда будет вызывать foo , даже если результат никогда не будет чем-то другим, кроме NaN .
Предыдущая модель обхода пост-порядка остается в силе. Однако после посещения левого поддерева оператора короткого замыкания язык решает, нужно ли оценивать правый операнд. Если нет (например, потому что левый операнд || уже истинен), результат возвращается напрямую, без посещения правого поддерева.
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()); //называется C // true
Вычисляется только C() , несмотря на то, что && имеет более высокий приоритет. Это не значит, что || имеет более высокий приоритет в этом случае — именно потому , что (B() && A()) имеет более высокий приоритет, это приводит к тому, что им пренебрегают в целом. Если он перестроен как:
console.log(A() && C() || B()); //называется A //называется B // false
Тогда эффект короткого замыкания && предотвратит оценку только C() ,но поскольку A() && C() в целом false , B() все равно будет оцениваться.
Однако обратите внимание, что короткое замыкание не меняет окончательный результат оценки. Это влияет только на оценку операндов , а не на то, как операторы сгруппированы — если оценка операндов не имеет побочных эффектов (например, запись в консоль, присвоение переменных, выдача ошибки), короткое замыкание не будет наблюдаться при все.
JavaScript
The-это логический оператор,который возвращает свой операнд правой стороны,если левая сторона нулевая,не определена,иначе Это можно рассматривать как частный случай логического оператора
Объекты можно инициализировать с помощью new Object.create()или литеральной нотации (инициализатор Инициализатор объекта-это выражение,описывающее инициализацию.
В следующей таблице перечислены операторы в порядке от наибольшего старшинства (18)к наименьшему Несколько примечаний о таблице:Последнее изменение:17 августа 2022 года,авторский коллектив MDN
Дополнительный оператор цепочки позволяет читать значение свойства,расположенного глубоко внутри связанных объектов,без необходимости проверять достоверность каждой ссылки.