
Благодаря широкому распространению и совместимости с различными платформами JavaScript стал основополагающим языком в современной веб-разработке. Но это также сложный язык со множеством неожиданных вывертов. В этой статье мы рассмотрим особенности JavaScript, способные по-настоящему озадачить.
Готовы к выносу мозга?
Оператор == и приведение типов
2 == [2] // true
Оператор == в JS выполняет приведение типов. Это означает, что перед выполнением сравнения он пытается привести сравниваемые значения к общему типу данных.
В нашем примере и число 2, и массив [2] преобразуются в строки. В результате оба значения равны 2. Поэтому в результате сравнения мы получаем true.
Обычно рекомендуется использовать оператор строгого равенства === вместо ==, чтобы избежать неожиданных результатов из-за приведения типов.
Другие примеры:
'123' == 123 // true 'foo' == NaN // false undefined == null // true NaN === NaN // false NaN == NaN // false 0 == null // false
Сравнение пустых массивов
[] == ![] // true
Здесь мы сравниваем пустой массив с булевым значением, которое было создано путем отрицания пустого массива с помощью оператора !. Результатом сравнения является true, что может показаться неожиданным на первый взгляд…
В JS каждое значение может быть либо истинным, либо ложным в булевом контексте. Пустой массив является истинным значением, то есть в булевом контексте он считается true. Когда мы применяем к нему оператор !, значение преобразуется в false.
С другой стороны, булево значение, созданное отрицанием непустого массива, — false. Когда мы сравниваем пустой массив со значением false с помощью оператора ==, JS пытается привести значения к общему типу перед их сравнением. При этом пустой массив преобразуется в false, в результате чего обе стороны будут ложными. В конце сравнение возвращает true.
Другие интересные примеры:
['a', 'b'] !== ['a', 'b'] // true ['a', 'b'] == ['a', 'b'] // false [1, 2] + [3, 4] // "1,23,4"
Сравнение null и undefined
null == undefined // true
Оператор == используется для сравнения двух значений на равенство. При этом он игнорирует типы данных сравниваемых значений. Если сравнивать null и undefined с помощью оператора ==, в результате получим true. Дело в том, что и null, и undefined представляют собой отсутствие значения и поэтому эквивалентны друг другу в данном контексте.
А вот с оператором строгого равенства мы получим false:
null === undefined // false
typeof
typeof NaN // number typeof null // object
В JS typeof — это оператор, используемый для определения типа значения или переменной.
NaN означает Not a Number («не число») и является специальным значением, представляющим неопределенное или непредставимое числовое значение.
Когда вы используете typeof с NaN, он вернет число. Это может показаться странным, но в JS NaN технически является числовым типом данных, даже если он представляет то, что на самом деле не является числом.
Когда typeof применяется к null, он возвращает строковый объект. Это происходит потому, что null считается специальным значением, которое представляет собой пустую ссылку на объект. null — это не объект, а скорее примитивное значение. Это считается одной из странностей JavaScript.
Другие примеры:
typeof function(){} // "function"
null instanceof Object // false
Сравнение булевых значений
true == "1" // true false == "0" // true
JS преобразует строку 1 в булево значение true, а строку 0 — в false. Таким образом, сравнение преобразуется в true == true, что дает true, и false == false, что тоже дает true.
Другие примеры:
1 + true // 2 1 - true // 0 '' == false // true 0 == false // true true + false // 1
Сложение и вычитание строк и чисел
"1" + 1 // "11" 2 + "2" // "22" "5" - 3 // 2
Когда вы используете оператор + со строкой и числом, число преобразуется в строку и конкатенируется со строкой.
Если строка может быть распознана как число, то число вычитается из строки.
Таким образом,
"1" + 1становится строкой «11»2 + "2"становится строкой «22»"5" - 3превращается в число 2
Другие примеры:
+"1" // 1 -"1" // -1 +true // 1 -true // -1 +false // 0 -false // -0 +null // 0 +undefined // NaN 1 / "2" // 0.5 "2" / 1 // 2 1 / 0 // Infinity -1 / 0 // -Infinity 3 * "abc" // NaN true > false // true undefined + 1 // NaN undefined - 1 // NaN undefined - undefined // NaN undefined + undefined // NaN null + 1 // 1 null - 1 // -1 null - null // 0 null + null // 0 Infinity + 1 // Infinity Infinity - 1 // Infinity Infinity - Infinity // NaN Infinity + Infinity // Infinity Infinity / Infinity // NaN
baNaNa
Здесь происходит конкатенация строки b, строки a, строки, полученной из выражения +"a", и строки a.
Выражение +"a" превращает строку a в число, которое оценивается как NaN, поскольку a не является валидным числом.
При конкатенации b, a, NaN и a мы получаем строку baNaNa.
Сравнение пустых объектов
!{} // false
{} == !{} // false
{} == {} // false
Тут мы сравниваем пустой объект {} с отрицаемым пустым объектом !{}. Восклицательный знак ! — это логический оператор НЕ, который отрицает значение объекта. Поскольку объект в JavaScript считается истинным, !{} возвращает false. Фактически мы сравниваем {} с false, что приводит к ложному значению, поскольку они не равны по значению или типу данных.
В последнем выражении мы сравниваем два пустых объекта {}. Несмотря на то, что они могут казаться одинаковыми, это два отдельных объекта с разными ссылками в памяти, поэтому они не равны по значению или типу данных. В итоге сравнение также дает нам false.
Когда вы используете оператор сложения + между двумя объектами, заключенными в фигурные скобки {}, он пытается объединить объекты как строки.
Другие примеры:
{} + [] === "" // false
!!{} // true
!![] // true
[] + [] // ""
[] + {} // "[object Object]"
{} + [] // "[object Object]"
{} + {} // "[object Object][object Object]"
[] == false // true
!!'' // false
!!0 // false
!!null // false
!!undefined // false
Операторы больше/меньше
7 > 6 > 5 // false
Сначала 7 > 6 оценивается как true, потому что 7 больше 6.
Затем оценивается true > 5. В JS true преобразуется в число 1, а false — в 0. Таким образом, 1 > 5 дает false, поскольку 1 не больше 5.
В итоге 7 > 6 > 5 эквивалентно true > 5, что ложно.
Другие примеры:
5 < 6 < 7 // true 0 > null // false
Math.max() и Math.min()
Math.max() // -Infinity Math.min() // Infinity
Функции Math.max() и Math.min() можно использовать для нахождения наибольшего и наименьшего значений в наборе чисел.
При вызове без каких-либо аргументов Math.max() возвращает -Infinity, что представляет собой наименьшее возможное число в JS. А Math.min() возвращает Infinity, что представляет собой наибольшее возможное число в JS.
Такое поведение имеет смысл, потому что если не предоставлены числа, то Math.max() не может вернуть наибольшее, а Math.min() — наименьшее число.
parseInt()
parseInt('08') // 8
parseInt('08', 10) // 8
parseInt('0x10') // 16
parseInt('08') преобразует строку 08 в целое число 8. Если написать parseInt('08', 10), функция все равно вернет 8.
Это происходит потому, что второй параметр функции parseInt определяет используемую систему счисления (двоичную, восьмеричную, десятичную, шестнадцатеричную и т.д.). Если этот параметр не указан, parseInt попытается определить систему счисления на основе формата строки. В приведенном выше примере 08 считается восьмеричным числом, поскольку оно начинается с 0, поэтому оно преобразуется в десятичное число 8.
parseInt('0x10') преобразует шестнадцатеричную строку 0x10 в целое число 16. Система счисления также не указана, но префикс 0x указывает на то, что число должно рассматриваться как шестнадцатеричное, поэтому оно преобразуется в десятичное число 16.
Другие примеры:
parseFloat('3.14.15') // 3.14
parseFloat('0.0') // 0
Удаление переменной внутри функции
(function(x) { delete x; return x; })(1); // 1
Анонимная функция принимает аргумент x. Внутри функции мы пытаемся удалить переменную x. Но это невозможно, поскольку x является аргументом функции и не может быть удалена. Затем функция возвращает значение x.
Когда эта функция вызывается с аргументом 1, значение x внутри функции устанавливается в 1. Операция удаления не имеет эффекта, функция просто возвращает значение x, равное 1.
Дополнительно
for (var i = 0; i < 3; ++i) {
setTimeout(() => console.log(i), 1000); // returns 3 three times
}
for (let i = 0; i < 3; ++i) {
setTimeout(() => console.log(i), 1000); // returns 0 1 2
}
var создает связывание в области видимости функции, поэтому после односекундного тайм-аута цикл уже завершился. Мы получаем число 3 трижды. Используя let, мы привязываем переменную в области видимости блока (цикла). Поэтому и получаем ожидаемые значения, так как i ссылается на значение в данной итерации цикла.
От редакции Techrocks: о var и let читайте в статье «Var, Let и Const: в чем разница?».
Перевод статьи «Unexpected Moments of JavaScript That Will Challenge Your Understanding of the Language».



В сравнении пустых объектов же
{} + [] // 0
{} + {} // NaN
В сравнении пустых объектов же
{} + [] // 0
{} + {} // NaN