IF Statement в JS

Об 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 как ссылку на выражение.

  • далее 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 состоит из:

        • [[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.

Habrahabr.ru прочитано 3948 раз