Несколько малоизвестных фактов об операторах для изучающих JavaScript.

Когда я впервые начал изучать JavaScript, я попробовал подход в стиле ускоренного курса. Я уже познакомился с программированием на других языках (таких как C, Python и т. д.). Естественно, я подумал, что мне просто нужно просмотреть синтаксис, а затем заняться кодированием. Я распечатал краткую шпаргалку, чтобы сэкономить бумагу, и начал выучивать содержимое наизусть. Это сэкономило время, и я смог быстро приступить к написанию кода на JavaScript. А за те мелочи, которыми я занимался (такие как настройка существующего кода для использования его на моей веб-странице, добавление кнопок и обработка простых событий или небольшие манипуляции с DOM для настройки его для веб-страницы), я не сталкивался много проблем. Все изменилось, когда я попал в React. Требовалось более глубокое понимание языка и его концепций. Именно тогда я начал делать свои собственные заметки, удостоверившись, что уловил нюансы, которые часто упускаются в этих начальных шпаргалках. Здесь я делюсь теми менее известными или часто упускаемыми из виду концепциями, связанными с операторами в JavaScript. Надеюсь, это поможет тем, кто, возможно, пропустил их, как я изначально.

Унарный | Бинарный | Тернарные операторы

Уроки по операторам касались «специального» троичного оператора, который имеет синтаксис: условие ? выражениеIfTrue : выражениеIfFalse. Но я никогда не задавался вопросом, почему имя такое. Ну, это только позже я обнаружил, что латинская приставка ter- обозначает три/трижды. А потом я заметил, что в этом выражении три термина, и я мог связать имя этого оператора с его синтаксисом. Потом мне стало интересно, какие есть другие подобные типы.

Я обнаружил, что такое соглашение об именах основано на количестве операндов, то есть на значениях, над которыми выполняется операция. Таким образом, некоторые операторы являются бинарными операциями, которые работают с двумя операндами, например арифметические операторы [например, возведение в степень (**), умножение (*), деление (/), модуль или остаток (%), сложение (+), вычитание (-)], операторы сравнения [например, больше (›), меньше (‹), больше или равно (›=), меньше или равно (‹=)], логические операторы [например, логическое И (&&), логическое ИЛИ (||)] и т. д. Подобным образом унарные операторы работают только с одним операндом. Оператор 'typeof' (typeof ____ ), оператор 'delete' (с помощью которого мы удаляем свойство Object), логический оператор NOT (!____), инкремент (____++), декремент (____- -) и т. д. примеры унарных операторов.

Здесь я хочу особо отметить + и -. Они могут быть либо бинарными, либо унарными операторами в зависимости от варианта использования, например:

let a = 5, b = 3; 
let sum = a + b; // + is used as a binary operator here

let myString = "5";
let myNumber = +myString; // + is a unary operator here
console.log(typeof myString); // -> string
console.log(typeof myNumber); // ->  number

let myNewNumber = -myNumber;
console.log(myNewNumber); // -> -5 

Унарный оператор плюс (+) предшествует своему операнду и оценивается как его операнд, но пытается преобразовать его в число, если это еще не сделано. И унарный оператор отрицания (-) предшествует своему операнду и инвертирует его, и, подобно +, он пытается преобразовать значение в число, если оно еще не является числом, поэтому они оба действуют как функция Number(____).

Тернарный оператор вместо блока If-Else

В некоторых случаях тернарный оператор (также называемый условным оператором) предпочтительнее условной логики if-else. Тернарный оператор не только сокращает код, но также полезен, когда нам необходимо использовать одно выражение вместо многострочного блока кода. Например, в ReactJS оператор возврата метода рендеринга использует выражения JSX, мы можем включать код JavaScript в фигурные скобки, но он должен быть ограничен одним выражением. Если нам нужно применить к нему какую-либо условную логику (например, в случае условного рендеринга), мы используем тернарный оператор, который представляет собой отдельное выражение, а не блок кода.

render(
  return({
    isUserRegistered ? <LogInScreen /> : <SignUpScreen /> 
  });
);

Кроме того, помните, что мы можем даже сделать вложение внутри тернарного оператора для случаев, когда нам нужно реплицировать вложенный цикл if-else.

Исключение из приоритета слева направо

Прежде чем обсуждать исключение, позвольте мне сначала сформулировать правило приоритета. Операции, соответствующие операторам, выполняются в порядке их старшинства. Что касается арифметических операций, то я, как и большинство людей, напортачил с правилом PEMDAS, которое легко напоминает порядок старшинства Скобки › Возведение в степень › Умножение, Деление › Сложение, Вычитание. Обратите внимание, что я использовал запятую (,) и не более (›) между умножением и делением, а также между сложением и вычитанием. Поскольку умножение или сложение не имеют приоритета над делением или вычитанием, они имеют тот же уровень. А когда несколько операторов имеют одинаковый уровень приоритета, операция выполняется слева направо. Однако обратите внимание, что из-за исторической причуды исключение из этого правила возникает, когда задействованы операторы возведения в степень (**). В наборе операций возведения в степень крайний правый оператор применяется первым.

let result = 2 ** 3 ** 2;
// this operation works like (2 ** (3 ** 2)) rather than ((2 ** 3) ** 2)
console.log(result); // -> 512, and not 64

Таким образом, в этом случае или даже вообще всегда лучше использовать набор круглых скобок, чтобы обеспечить предполагаемую логику в фактических вычислениях. Кроме того, обратите внимание, что, хотя это и не охватывается аббревиатурой PEMDAS, оператор по модулю или остатку (%) имеет тот же уровень приоритета, что и умножение и деление. Если вы сомневаетесь, обязательно обратитесь к полному списку приоритетов операторов, например эта таблица в MDN Docs.

Различие между равенством и строгим равенством

Я хочу быстро коснуться разницы между оператором равенства (==) и оператором строгого равенства (===). Помните, что может иметь место приведение типов, когда мы оцениваем равенство с помощью ==. Например, 0 и пустая строка приравниваются к false с оператором == из-за преобразования типа.

console.log(false == 0); // -> true
console.log(false === 0); // -> false 

console.log('' == 0); // -> true
console.log('' === 0); // -> false

console.log('' == false); // -> true
console.log('' === false); // -> falseInteresting fact: only one value in JavaScript is not equal to itself. NaN denotes the result of a nonsensical computation, hence cannot be equivalent to any other nonsensical computation.

Уместно упомянуть, что даже null приравнивается к undefined при проверке с помощью оператора ==, но ни один из них не равен (==) false, 0 или пустой строке, хотя все они (undefined, null, 0, «») являются ложными значения вместе с NaN и ложью, конечно.

console.log(null == undefined); // -> true

console.log(null == false); // -> false
console.log(null == 0); // -> false
console.log(null == ''); // -> false

console.log(undefined == false); // -> false
console.log(undefined == 0); // -> false
console.log(undefined == ''); // -> false

Поэтому при сравнении рекомендуется использовать точное равенство, а не обычное равенство, т. е. === и !==, а не == и !=, чтобы предотвратить неожиданное преобразование типов. Однако помните, что иногда, когда вы записываете пользовательский ввод, например, в браузере с всплывающим окном типа функции подсказки (___) или в CLI с readline-sync, убедитесь, что вы конвертируете захваченные данные (которые сохраняются в строковых данных). type) в число, если вы собираетесь проверять строгое равенство с другим числовым типом. Представьте, что пользователь ввел 5, вы сохраняете его в некоторой переменной userInput, и без преобразования userInput в число, если вы проверите условие (userInput === 5), вы получите ложный результат, и это будет логическая ошибка.

Глупая ошибка: иногда, когда вы намереваетесь написать ==, если вы напечатаете его как просто математический знак равенства, он будет работать как оператор присваивания. В этом случае в основном условия всегда будут истинными, если только правая часть присваивания не является «ложным» значением. См. следующие примеры:

let x = 55;
if (x = 54) {
 console.log("this code is executed.");
}
// -> this code is executed.

let y = 0;
if (y = 0) {
 console.log("this is the desired action");
}
// -> 
// Nothing is logged in console 
// because y is first assigned a falsy value
// and then y itself is evaluated as a condition, 
// so the falsy value of y is evaluated as false.

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

console.log(NaN == NaN); // -> false

Третий аватар со знаком плюс (+)

Мы уже обсуждали выше, что + может быть унарным или бинарным оператором в зависимости от контекста. Интересно, что этот знак может функционировать по-другому, когда находится внутри console.log(____). Там первая проверка рассматривает его как оператор конкатенации строк. Если не логично, то только работает как оператор сложения, как видно из следующих примеров:

console.log("5" + "5"); // -> 55
console.log(5 + "5"); // -> 55
console.log(5 + 5); // -> 10

Что ж, если вы поняли изложенную выше концепцию, вот вам небольшая забавная викторина. Как вы думаете, каков ожидаемый результат для следующего? Пишите свои ответы в комментариях под этим постом в блоге.

// Fun Quiz | Write your answers in the comments
console.log(5 + "5" - 3);
// What is the expected output for the above line of code?

Короткое замыкание логических операторов (&& и ||)

Это явление гарантирует, что для кода существует кратчайшая возможная схема (т. е. с минимальным количеством необходимых вычислений или сравнений). Следовательно, логический оператор ИЛИ (т. е. ||) не будет оценивать, что находится справа, когда левая сторона дает истинное значение (это означает, что он не будет назначать переменные или запускать функции или выполнять какой-либо код, если они находятся справа). сторона после того, как левая сторона оценивается как истинная). Точно так же логический оператор И (то есть &&) не будет оценивать то, что находится в правой части, когда левая часть уже оценивается как ложная. Итак, ниже приведены различные сценарии короткого замыкания:

let x = "right-side";
console.log( true || x ); // -> true [x is never read.]
console.log( false || x ); // -> right-side [i.e., x.]
console.log( true && x ); // -> right-side [i.e., x.]
console.log( false && x ); // -> false [x is never read.]

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

// An example code that uses an if-conditional:

testCondition = true;

if (testCondition === true) {
  console.log("Desired action."); // -> Desired action.
}

// Note that the above code could be refactored like so:

testCondition = true; 

if (testCondition) {  // -> Test the boolean variable directly w/o '=== 0'
  console.log("Desired action."); 
}

// Even shorter code using short-circuiting of logical operators:

testCondition = true; 

testCondition && console.log("Desired action.");  // -> Desired action.

// The compiler reads/executes the right side only if testCondition is true

Нулевой оператор объединения ( _ ?? _ )

Этот бинарный оператор представлен парой вопросительных знаков (??) между двумя сторонами операнда — правая часть этого оператора выполняется, только если левая сторона имеет значение null или undefined. В противном случае возвращается левая сторона.

// Syntax of the Nullish Coalescing Operator
a ?? x;
// resturns 'x' only if 'a' is undefined or null, else returns 'a' 

Но, только что сославшись на короткое замыкание логических операторов выше, вы можете задаться вопросом, зачем это нужно, если мы можем использовать короткое замыкание логического ИЛИ (т. е. ||) вместо него, как показано ниже. Представьте себе, что проводится опрос различных домохозяйств по различным вещам, которыми они владеют, а также по количеству автомобилей. И учтите, что они вводят null, если машины нет.

let numCars = null; 
console.log(`The family has ${ numCars || "no" } car(s).`);
// -> The family has no car(s).
console.log(`The family has ${ numCars ?? "no" } car(s).`);
// -> The family has no car(s).

numCars = 2;
console.log(`The family has ${ numCars || "no" } car(s).`);
// -> The family has 2 car(s).
console.log(`The family has ${ numCars ?? "no" } car(s).`);
// -> The family has 2 car(s).

Вышеупомянутые случаи выглядят логически правильными при использовании короткого замыкания || оператор или нулевой оператор объединения. Есть улов; null на самом деле не похож на 0. Также могут быть случаи, когда геодезист по какой-то причине не мог знать или записать значение ‘numCars’, поэтому данные остаются неопределенными. В этом случае номер по умолчанию должен быть представлен как неизвестный. Теперь давайте посмотрим, какая разница между двумя способами в таких пограничных случаях:

let numCars = undefined; 
console.log(`The family has ${ numCars || "unknown number of" } car(s).`);
// -> The family has unknown number of car(s).
console.log(`The family has ${ numCars ?? "unknown number of" } car(s).`);
// -> The family has unknown number of car(s).

numCars = 0;
console.log(`The family has ${ numCars || "unknown number of" } car(s).`);
// -> The family has unknown number of car(s).
console.log(`The family has ${ numCars ?? "unknown number of" } car(s).`);
// -> The family has 0 car(s).

Поскольку мы изменили текст в правой части опции, чтобы позаботиться о неизвестных данных (неопределенное значение), два метода хорошо работали только для неопределенного значения. Но когда количество автомобилей известно как 0, эта конструкция не работает с методом короткого замыкания, создавая логическую ошибку. Он оценивает 0 как ложный в левой части, а затем выполняет правую часть и печатает текст «неизвестный номер» автомобиля, тогда как на самом деле номер известен и равен = 0. И только нулевой оператор объединения выводит там логически правильную информацию. Итак, уникальность нулевого оператора объединения заключается в том, что он может различать null или undefined и набор других «ложных» значений, таких как 0, пустая строка, ложь и т. д. Иногда такие значения, как 0, пустая строка , а false могут быть допустимыми ответами или входными данными для чего-либо. Но метод короткого замыкания игнорирует эти допустимые входные данные, поэтому для проверки этих ответов необходим нулевой оператор объединения.

let response = false; // which is a possible and perhaps valid response
console.log( false ?? 'default'); // -> false
console.log( false || 'default'); // -> default

Необязательный оператор цепочки ( _ ?. _ )

Необходимости в этом может и не возникнуть при написании простых программ. Но при выборке данных, включающих слои объектов (вложенные объекты), необязательный оператор цепочки действительно полезен. Давайте рассмотрим сценарий, чтобы лучше понять это. Рассмотрим набор данных с записями о различных предприятиях, такими как информация о компании, контакты, финансовые данные и т. д. Теперь есть вероятность, что все записи не будут полными. В некоторых бизнес-записях может отсутствовать информация по некоторым полям данных. Теперь, если вы отображаете список этих предприятий с их контактной информацией на веб-странице, вы не знаете, в каких полях данных отдельных бизнес-объектов есть такие отсутствующие данные. У некоторых могут отсутствовать адреса электронной почты или номера телефонов; у некоторых могут быть записаны оба данных, тогда как у других может не быть ни одного.

// Suppose you write the below code to render the info on the front end.

let businesses; // = fetched data, array of info for each entity

businesses.forEach((business) => {
  let phoneNum =  business.contacts.phone;
  let emailAddress = business.contacts.email;
  let businessId = business.companyId;
  let businessDiv = document.querySelector("#company-[businessId]");
  businessDiv.querySelector('.phone').innerText = phoneNum;
  businessDiv.querySelector('.email').innerText = emailAddress;   
});

Приведенный выше код может вызывать ошибки при оценке связанных свойств во вложенных объектах, таких как «business.contacts.email» и «business.contacts.phone» — если объект контактов отсутствует или не определен, ошибка будет отображаться как свойства ( электронная почта или телефон) из неопределенного не может быть прочитан. В этом случае нам будет лучше, если мы не получим ошибку. Скорее мы хотим, чтобы значение не было определено при отсутствии. Мы можем позаботиться об этом, написав:

...
...
    let phoneNum =  business.contacts?.phone;
    let emailAddress = business.contacts?.email;
...
...

Итак, если объект контактов существует, мы получаем значение свойства «телефон» или «электронная почта». Если же объект контактов не существует или не определен, то вместо ошибок мы получаем как минимум неопределенные значения. Мы можем показать текст, соответствующий отсутствующей информации, вместо того, чтобы выдавать уродливую ошибку в пользовательском интерфейсе. Обратите внимание, что мы можем даже сделать необязательную цепочку для каждого вложенного уровня объектов. Поэтому рекомендуется писать так «бизнес?.контакты?.телефон», чтобы охватить все уровни.

Конец примечания: участие читателей приветствуется!

Эти тонкости случаются нечасто, поэтому ими часто пренебрегают. Всякий раз, когда я их наблюдал, я пытался запечатлеть их в своих заметках. И оттуда же я привел указатели выше. Я надеюсь, что эти тонкие факты, исключения и предупреждения помогут вам лучше выучить язык. Но мой опыт ограничен, я мог пропустить несколько тем. Пожалуйста, не стесняйтесь добавлять в комментарии ниже, если вы столкнетесь с такими причудливыми проблемами. Просто убедитесь, что они связаны с операторами, потому что тема этого поста в блоге как раз об этом.