javascript

IF Statement в JS

  • понедельник, 7 апреля 2025 г. в 00:00:04
https://habr.com/ru/articles/897884/

Об IF-Statement (введение)

IF-Statement - это конструкция для некоторого условного выполнения кода. Она позволяет выполнить определенный блок кода в зависимости от того истинно или ложно некоторое условие и согласно официально спецификации ECMAScript, syntax if-statement можно записать как:

if (условие) действие
if (условие) действие1 else действие2

То есть можно записать как с ELSE так и собственно без ELSE.

  • IF с английского языка переводится как "если".

  • ELSE с английского языка же переводится как "иначе" или еще говорят "в противном случае".

Примечание (dangling else)

Понятие Dangling ELSE (или как еще многие говорят висящий else) связано с неоднозначностью в интерпретации условных конструкций if-else, когда в коде есть вложенные условия, и неясно, к какому if относится блок else. Фактически все это сводится к проблеме синтаксиса, которая возникает из-за отсутствия явного указания границ блоков (то есть фигурных скобок {..} ) или неверного их оформления.

Пример dangling else:

let x = 5;
if (x > 1)
    if (x % 2 !== 0)
        if (x !== undefined) 
            console.log(`x is ${x}`);
else 
    console.log('x is not greater than 1');

if (x & 1) 
    if (x === undefined)
        console.log(`(1) x is ${x}`);
else
    console.log('----');

На примере выше два блока кода if-else. Во-первых подумайте что выдаст нам такой код? Верно результат выполнения будет таким:

результат выполнения
результат выполнения

Из этого мини-анализа, можно понять что если писать код в таком виде, то else будет привязываться к ближайшему внутреннему if. То есть:

  1. В первом блоке else привязан к if (x !== undefined), а не к if (x > 1), как могло бы быть задумано.

  2. Во втором блоке else привязан именно уже к if (x === undefined), а не к if (x & 1).

Вы сразу вероятно зададите вопрос как решить проблему dangling else? Как на максимально себя обезопасить от этой заявленной проблемы? Все проще чем вы думаете. Просто пишите постоянно фигурные скобки {} - так вы более явно/четко обозначите границы внутренностей.

Вот полный, более правильный (модифицированный) код:

let x = 5;
if (x > 1) {
    if (x % 2 !== 0) {
        if (x !== undefined) {
            console.log(`x is ${x}`);
        } else {
            console.log(`x is undefined`);
        }   
    } else {
        console.log('x is even');
    }
} else {
    console.log('----');
}
    
if (x & 1) {
    if (x === undefined) {
        console.log(`(1) x is ${x}`);
    } else {
        console.log('x is not undefined'); 
    }
} else {
    console.log('----'); 
}
    

IF-Statement в JS работа изнутри

Опираясь прямо на спецификацию ECMAScript, пусть есть:

if (exp) Statement else Statement

Как же он работает изнутри:

  • сначала вычисляется некоторое выражение exp которое находится в скобках.

    expRef = Evaluation(exp)

    результат этого вычисления сохраняется в expRef как ссылку на выражение.

  • далее преобразуется уже в булево значение
    expVal = ToBoolean(GetValue(expRef))

    через абстрактную операцию ToBoolean. Но перед этим идет процесс получения значения по ссылке expRef.

    работает ToBoolean очень просто:

    работа ToBoolean из спецификации
    работа ToBoolean из спецификации
    • Если переданный аргумент уже является Boolean, то сразу вернется этот аргумент.

    • Если аргумент один из так называемых Falsy Values (undefined, null, +-0, 0, NaN или пустая строка), то вернется false.

    • Во всех остальных случаях вернется true.

    после чего в expVal уже будет хранится либо true, либо false.

  • затем идет проверка условия

    • если условие истинно (то есть true), то будет выполнен первый Statement (то есть блок после if).

    • если условие ложно (то есть false) в таком случае будет выполнен уже второй Statement (то есть блок else).

    • причем результаты выполнения сохраняются как stmtCompletion

      stmtCompletion = Completion(Evaluation(Statement))

      • stmt - такое сокращение от "Statement" (оператор, утверждение). И во всей спецификации ECMAScript - такое понятие Statement - это фактически оператор.

      • А вот stmtCompletion это уже специальный объект типа Completion Record, который как раз представляет результат выполнения нашего Statement.

        из спецификации "Completion Record"
        из спецификации "Completion Record"

        Completion Record состоит из:

        • [[Type]] - это тип завершения (например normal, break, ..).

        • [[Value]]- возращаемое значение (empty, если ничего не возвращается).

        • [[Target]] - метка.

    • и в конце идет во

      Return ? UpdateEmpty(stmtCompletion, undefined)

      фактически возвращаем результат с гарантией, что там не empty (если пусто, то вернется undefined)

Примечание-2.

И вот как раз если блок ELSE отсутствует по каким-либо причинам, а условие при этом ложно, то результат вернет undefined.

А как же наше ELSE IF??

ELSE IF (как еще говорят "иначе если") - не существует как отдельная конструкция согласно спецификации. ELSE IF - это комбинация существующего ELSE и вложенного IF, и служит эта вся комбинация исключительно как возможность делать дополнительные условия. Можно еще сказать что ELSE IF - это некоторая цепочка else с новым if внутри, для проверки доп. условий.

Пример кода:

let x = 6;

if (x > 0) {
    ++x;
    console.log(x);
} else if (x < 0) {
    --x;
    console.log(x);
} else {
    x *= 47127
    console.log(x);
}

Примечание-3.

Еще кстати для объединения нескольких условий используются логические операторы.

Термин "опущенные фигурные скобки"

Без фигурных скобок следующая строка после if уже считается его телом, а остальные строки операторами.

И вот вам простой пример:

let x = 23714;

if (x > 0)
    x += 34815;
    x *= -1;

console.log(x); // -58529

Здесь истинное условие заставляет нас выполнить сложение с присваиванием, а затем идет умножение с присваиванием. Так вот *= -1 будет выполнятся всегда! А += 34815 только при истинности условия.

И более правильнее (если мы хотим чтобы *= выполнялось только при истинности условия) ставить фигурные скобки - разделяя логику - пример чуть модифицированный правда:

let x = 23714;

if (x < 0) {
    x += 34815;
    x *= -1;
}    

console.log(x); // 23714

Проблемы написания кода без фигурных скобок

  • dangling else

  • различные ошибки при добавлении новых строк кода

  • также снижение читаемости всего кода

  • и другие..

Замечание (и совет):

Лучше всегда ставить фигурные скобки {..} !!!

Нестандартная работа с логическими операторами

Раз уж упомянул их выше, давайте и тут быстренько разберемся. Как вы привыкли предсказывать куда указатель зайдет в if, или же в else? Допустим есть пример кода - вот такой:

if (1 && "abcd" && 12) {
    console.log(1);
} else {
    console.log(2);
}

С точки зрения большинства (а именно так думает практически 70-90% js-разработчиков, более точных данных у меня к сожалению нет), мы заходим в if, и тут получается 1 - это true, "abcd" - true, 12 - это тоже true -> значит true && true && true -> на выходе дает нам true. Ой как здорово у нас ИСТИНА, значит заходим в if, и выводим в консоль цифру 1.

НО

Все работает вообще не так! Вспоминаем работу IF-Statement.. да-да то что я описывал выше про Evaluation, про абстрактный ToBoolean и другое.

  • берется exp

  • вычисляется expRef

  • далее вычисляется expVal как результат от ToBoolean(GetValue(expRef))

Итак, работает все примерно так:

  • вычисляем какое у нас exp

  • начинаем слева 1 && "abcd"

    • по ToBoolean(1) --> true

    • по ToBoolean("abcd") --> true

      и получается в голове у нас true && true - и согласно спецификации если слева стоит true, нужно вернуть правую часть. И возвращаем мы не true, а именно значение "abcd".

  • далее уже "abcd" && 12

    • ToBoolean("abcd") --> true

    • ToBoolean(12) --> true

      слева снова стоит true возвращаем правую часть, получается 12.

  • таким образом, наш exp=12

  • ToBoolean(12) --> true - значит заходим в if.