Тернарные выражения или Как я научился не волноваться и полюбил ?:

Около трех месяцев назад я решил стряхнуть пыль со своей давней мечты почти десятилетней давности и вернуться к программированию. Мой предыдущий опыт программирования был на C++, а в настоящее время я изучаю JavaScript, но, к счастью, компьютеры по-прежнему думают так же, как и в начале нулевых. Они просто тоньше и быстрее и постоянно пытаются украсть вашу личную информацию, как новый стажер в вашей компании, который, как вы знаете, охотится за вашей работой и продолжает сплетничать о вас с вашим боссом. Черт возьми, Колин, я нанял тебя!

В первые дни изучения языка вы чувствуете себя поглощенным как это сделать, например, "Как я могу заставить эту функцию возвращать сумму чисел во вложенном массиве?" Мы принимаем как должное многие правила и логику, которые тихо работают на нас в языке программирования, поэтому мы можем двигаться вперед в нашем обучении. Одна из захватывающих вещей в прохождении этого начального этапа заключается в том, что вы можете вернуться назад и уточнить свое понимание конкретных деталей. Например, тернарный оператор. Это немного странный синтаксис, и на днях я понял, что, хотя я могу распознавать и использовать троичные выражения, я не был на 100% уверен в том, как они работают. Время глубокого погружения!

Что такое тернар?

Тернарный оператор ?: — единственный оператор JavaScript, принимающий три операнда. Поскольку мы углубляемся здесь, давайте определим некоторые термины:

оператор:символ, указывающий на то, что для получения значения необходимо выполнить определенную математическую, реляционную или логическую операцию

operand:объект или количество, над которым выполняется операция

Согласно MDN, синтаксис троичного выражения выглядит так:

condition ? exprT : exprF

Где операнд condition — это выражение, значение которого используется как условие, а операнды exprT и exprF — это выражения любого типа. Мужик, а можно ли в этом предложении уместнее использовать слово «выражение»? Лучше посмотри на это!

Что такое выражение?

Выражение — это любой фрагмент допустимого кода, результатом которого является некоторое значение. Давайте рассмотрим очень простой пример:

let x 
x = 10

Здесь x = 10 – это тип выражения, известный как выражение присваивания. Он использует оператор присваивания =, который принимает два операнда и следует следующим правилам:

  1. Присваивает значение своего левого операнда значению своего правого операнда
  2. Вычисляет значение своего правого операнда
  3. Является правоассоциативным (мы еще вернемся к этому).

Итак, в нашем примере x = 10 левый операнд равен x. Правый операнд 10. В соответствии с правилами, изложенными выше, мы можем наблюдать следующее поведение:

console.log(x = 12)
//Expected output: 12, because of Rule 2
console.log(x)
//Expected output: 12, because of Rule 1

Этот тип выражения имеет так называемый побочный эффект. То есть происходит что-то, что не является частью вычисления выражения. В данном случае это присвоение значения 12 нашей переменной x. Интересно, что x само по себе является чистым выражением без побочных эффектов! Он просто оценивает его значение.

Теперь поговорим о правиле 3: выражения присваивания правоассоциативны. Но что это значит?

x = 10 — очень простое выражение с одним оператором. Но, как вы, наверное, знаете, в выражениях может быть много операторов, и они могут даже использовать одни и те же операторы в разных местах.

x = 12 / 4 / 2 * 3 + 1 
console.log(x)
//Expected output: 5.5
x = (11 - 1) + (5 - 1) / 2 
console.log(x)
//Expected output: 12
x += 1 - 1
console.log(x)
//Expected output: 12

Если приведенные выше ожидаемые результаты не соответствуют вашим ожиданиям, то вы поняли, почему компилятору (программе, которая интерпретирует ваш код) нужны правила для работы с операторами. Правила вроде…

приоритет оператора:то, как операторы обрабатываются по отношению друг к другу.

ассоциативность как обрабатываются операторы с одинаковым приоритетом. Либо слева направо, либо справа налево.

Есть отличная таблица, где вы можете проверить ассоциативность и приоритет различных операторов здесь.

А теперь давайте оживим жизнь нашего простого маленького друга x = 10 :

let x = 10
let y = 5
let z = 1
console.log(x = y = z)
//Expected output: 1

Операторы присваивания правоассоциативны, то есть анализируются справа налево. Таким образом, первая часть этого выражения, с которой мы имеем дело, это y = z, и теперь мы знаем, что она оценивается как значение правого операнда z, то есть 1. Значение 1 становится правым операндом для следующего оператора присваивания с x в качестве левого операнда, и мы знаем, что это тоже будет оцениваться как 1. В качестве побочного эффекта x, y и z теперь также содержат значение 1!

Этот пример очень простой, но изучение каждой части сверхпростого выражения дает много интересной информации. Чтобы быть выражением, часть кода должна возвращать некоторое значение. Он может использовать операторы, которые выполняют какую-то операцию над своими операндами для получения этого значения. Операторы обладают свойствами приоритета и ассоциативности, что позволяет компилятору знать, как поступать со всеми различными операторами, которые могут появляться в выражении, и в конечном итоге получить это значение.

Что такое условие?

«Но как насчет тернарных выражений?!» ты говоришь. — Вот зачем я пришел сюда! И на это я говорю, мы добираемся туда! Давайте посмотрим на первую часть троичного выражения, операнд условия.

условие: выражение, которое можно преобразовать в истинное или ложное.

Мы уже знаем, что выражения должны возвращать какое-то значение. Но как тогда это значение преобразовать в истинное или ложное? Определим еще несколько терминов:

приведение типов преобразование значения из одного типа данных в другой.

Truthy: значения, которые преобразуются в значение логического типа true.

falsy: значения, которые преобразуются в значение логического типа false.

В ситуациях, когда ожидается истинное или ложное значение, например, в операторе if(...) или первом операнде троичного кода, неявное приведение типов преобразует результирующее значение выражения в истинное или ложное. Соответствует ли принуждение этого типа определенным правилам? Еще бы!

Ложные значения в JavaScript — это false, null, undefined, 0, NaN, '', "" и document.all.

Истинные ценности — это почти все остальное! true, [], {}, непустые строки и ненулевые числа.

Важно отметить, что неявное приведение типов, происходящее для логических контекстов в JavaScript, на самом деле не переназначает исходное значение.

let x = 10
if (x) {
   console.log("Hi!")
}
console.log(x)
//Expected output: 
// "Hi"
// 10

В приведенном выше примере значение x не меняется, но в логическом контексте оператора if оно принудительно устанавливается и оказывается истинным. Таким образом выполняется строка console.log("Hi!").

Теперь, когда мы углубились в приведение типов, этот операнд условия становится ясным: благодаря своему логическому контексту операнд условия в троичном выражении будет приведен к логическому значению true или false в зависимости от истинности/ложности значения, которое он оценивает. .

Всё вместе!

У нас есть выражения, у нас есть логический контекст, который запускает неявное приведение типов на основе истинных/ложных значений. И у нас есть этот удивительно красивый синтаксис с оператором ?:, который, как вы знаете, играет по своим собственным правилам. Что это за правила?

condition ? exprT : exprF
  1. Тернарные выражения оцениваются как значение exprT, если condition преобразуется в истинное, и exprF, если condition преобразуется в ложное.
  2. exprT и exprF могут принимать значения любого типа.
  3. Тернарный оператор правоассоциативен.
  4. Тернарные выражения оценивают либо exprT, либо exprF в зависимости от condition.

Это все хорошо, но каковы варианты использования троичного выражения? Ну, они отлично подходят для присвоения значений! Представьте контактный зоопарк, который дает скидки для детей до 12 лет. Мы можем рассчитать стоимость билета в зависимости от возраста:

function ticketPrice (age) {
   return age < 12 ? 5 : 10
}
let price = ticketPrice(8)
console.log(price)
//Expected output: 5

Выражение условия в приведенном выше примере — age < 12, в котором используется оператор сравнения <, возвращающий true, если значение левого операнда меньше значения правого операнда. 8 действительно меньше 12, поэтому возвращается значение 5.

Поскольку этим контактным зоопарком управляет эксцентричный миллиардер, для клиентов по имени Тайлер предусмотрены особые условия. Без проблем:

function ticketPrice (age, name){
  return name === "Tyler" ? 3 
       : age < 12 ? 5 : 10
}
let price = ticketPrice(15, "Tyler")
let price2 = ticketPrice(15, "Meg")
console.log(price, price2)
//Expected output: 3, 10

Помните, что тернарный оператор является правоассоциативным, поэтому связанное тернарное выражение в ticketPrice анализируется следующим образом при первом вызове функции ticketPrice(15, “Tyler"):

age < 12 ? 5 : 10 оценивается как 10, потому что age имеет значение 15. 15 не меньше 12, поэтому вычисляется выражение exprF.

name === "Tyler" ? 3 : 10 оценивается как 3, потому что этого ребенка на 100% зовут Тайлер.

Во втором вызове функции ticketPrice(15, "Meg") цена нашего билета оказывается равной 10, поскольку Мэг 15 лет, и на 100% имя не Тайлер.

Бонусный раунд: запятая-хамелеон

В поисках тернарного удовлетворения я наткнулся на еще один странный оператор, о существовании которого в JavaScript я даже не подозревал. Я думаю, вы бы назвали это выражением с запятой, и это просто список выражений, разделенных запятыми, вот так...

let y, z
let x = (y = 10, z = 40, "Hi!")
console.log(x, y, z)
//Expected output: "Hi!", 10, 40

Оператор запятой оценивает каждый из своих операндов и оценивает самый левый. В приведенном выше примере оценивается y = 10, затем z = 40 и, наконец, строка "Hi!". Поскольку "Hi!" — крайний левый операнд, выражение в круглых скобках оценивается как “Hi!”, и тогда мы возвращаемся на знакомую территорию выражения присваивания. Как вы уже догадались, преимущество оператора запятой — это побочные эффекты — теперь у нас установлены значения для y и z.

Этот оператор запятой может пригодиться для тройных чисел, поскольку exprT и exprF должны быть выражениями. В приведенном ниже примере у нас есть функция для обновления уровней и этапов игры в зависимости от того, правильно ли пользователь отвечает на вопрос. Если они отвечают правильно, мы хотим обновить этап или, если этап уже на 3, обновить уровень. Однако, если пользователь ответит на вопрос неправильно, мы хотим уменьшить этап или, если этап уже равен 1, уменьшить уровень. Если этап и уровень равны 1, мы хотим вернуть «ПОТЕРЯНО!» как для этапа, так и для уровня.

const updaterFunction = (answer) => answer ?
   (level, stage) => {
      stage === 3 ? (level++, stage = 1)
        : stage++
    return ({level: level, stage: stage})
   }
   : (level, stage) => {
      stage > 1 ? (stage--):
        level > 1 ? (level--, stage = 3)
       : (level = "LOST!", stage = "LOST!")
   return ({level: level, stage: stage})
 }

Узрите могучую мощь тернарных и запятых операторов, работающих вместе! Обратите внимание, что вы должны заключать выражения с запятыми в круглые скобки, чтобы они правильно анализировались как выражения внутри троичных элементов.

Вывод: троичные выражения — это круто

Я всегда подозревал, что это правда, но, изучив детали и потратив время на то, чтобы по-настоящему вникнуть в тему, я стал лучше понимать оператор ?: и JavaScript в целом. Тернарные выражения могут помочь сделать ваш код менее подробным, и они чертовски хорошо с этим справляются. Сияй, сумасшедший ?: !