Магия или простые правила?
«Где отсутствует точное знание, там действуют
догадки, а из десяти догадок девять — ошибки»
(с) Максим Горький
Данный материал, в первую очередь, вам не только поможет пройти собеседования на позицию Frontend разработчика, но вам лично прояснит как же все-таки работает магия в JS и почему. Эта статья может сделать вашу жизнь js-программиста более легкой. Для затравки вот вам задачка:
const bool = new Boolean(false);
if (bool) console.log(bool);
if (bool == false) console.log(bool);
Что выведет?
Забегая вперед, скажу что отработают оба условия, при этом везде будет вывод что-то типа Boolean{false}. Почему так? Плохой и непонятный JS? Да нет, JS очень даже хороший и понятный, просто нужно все это понять.
Вообще написание этого материала — это подготовка к одной хорошей и интересной теме, связанной с полиморфизмом и перегрузкой в JS. Но когда я писал статьи про перегрузку и полиморфизм понял, что есть много вещей, которые нужно объяснить до того, как расскажу про них иначе будет сложно. Поэтому будем идти по порядку.
Система преобразования типов в JS очень простая, хотя на первый взгляд этого и не скажешь. Она сильно отличается от других языков программирования — это факт, поэтому у многих программистов, пришедших из других языков программирования в JavaScript часто вызывает когнитивный диссонанс. Но если разобраться, то реально все просто. Нужно усвоить, что в JS есть всего 3 приведения типов:
- Строковое
- Численное
- Логическое
Строковое преобразование происходит при необходимости представления чего-либо в виде строки. Тут все просто.
Численное преобразование происходит в математических выражениях, а также при сравнении.
Логическое преобразование — приведение к true и false, происходит в логическом контексте(например в условиях) и при применении логических операторов. Все значения, которые можно трактовать как пустые, становятся false: 0, undefined, null, NaN, пустая строка.
Все остальное, в том числе и любые объекты — true.
В JavaScript логическое преобразование особенно интересно своим сочетанием с численным.
Дабы не писать Boolean, я заменю его на двойное преобразование !! — но оно не меняет сути и поведения.
Пример такой двусмысленности (если не знать правил):
const a = 0;
const b = ' 0 ';
console.log(!!a, !!b); // false, true
console.log(a == b); // true
Значение a в логическом контексте false, так как 0 — интуитивно пустое значение, как писалось выше. Значение b не пустое в логическом контексте, так как содержит строку из 3х символов (2 пробела и 0).
А когда мы сравниваем 0 и “ 0 ”, то мы сравниваем их не в логическом контексте, а в численном, поэтому у нас получается такое сравнение с преобразованием типов:
Number(0) == Number(" 0 ")
Таким образом нужно всего лишь запомнить, что в JavaScript есть 3 типа преобразований:
- String() — приведение к строке в строковом контексте (например при конкатенации строк).
- Number() — приведение к примитиву в численном контексте, включая унарный плюс (+value). Происходит при сравнении разных типов.
- Boolean() — приведение к логическому типу в логическом контексте.
Особым случаем является проверка на равенство таких специальных типов как Null и Undefined. Они равны только друг другу и неравны всему остальному. Это прописано в спецификации.
null == null // true
undefined == undefined // true
undefined == null // true
null == undefined // true
Возвращаясь к нашей задаче, которая была описана выше, так почему же так отработает код?
Если вы внимательно прочли статью, то теперь можете сами объяснить это. Давайте сверимся. Было дано:
const bool = new Boolean(false);
if (bool) console.log(bool);
if (bool == false) console.log(bool);
Мы используем класс Boolean, который создает не примитив, а объект — экземпляр класса Boolean. Для таких объектов в JS прописана своя модель обработки объектов в логическом контексте, о чем писалось выше. Если bool — это объект, то в логическом контексте, неважно что это за объект и от какого класса, он всегда будет приведен к true:
Boolean(bool) // = true
// поэтому
if (bool) // = true
И все логично и все подчиняется правилам. Никакой магии. При этом, если мы производим сравнение, то тут сработает численное сравнение, при котором будет вызван “магический” метод valueOf:
bool.valueOf() == false
// поэтому
bool == false // true
if (bool == false) // true
Про магические методы мы поговорим в следующей статье. И так, вы видите, что все логично и все подчиняется правилам. Никакой магии.
Зачем это знать?
Нет, не затем, чтобы проходить собеседования. Понимать, как устроен ваш язык программирования, как он работает и почему — это та отличительная черта настоящего профессионала программиста от ремесленника. Понимание таких особенностей, позволяет в разы сократить поиск и устранение ошибок. Позволяет не плеваться и говорить какой язык плохой, а делать вещи, которые изначально кажутся невозможными. Да банально для общего развития. Как говорил М.В. Ломоносов:
“Математику уже за то учить надо, что она ум в порядок приводит”
(с) М.В. Ломоносов
Если хотите быть профессионалом, вам стоит понимать, как устроен ваш инструмент. Это повышает вашу ценность. Но не обязательно, да.