Странные вещи, JavaScript Edition
Ссылка на оригинал - https://livecodestream.dev/post/2020-06-03-stranger-things-javascript-edition/, автор публикации - Live Code Stream
Найдите объяснение некоторым из самых странных моментов JavaScript в этой статье

Сегодня мы сделаем специальный пост, посвященный тем странным моментам в JavaScript, где код ведет себя немного странно.
"Никто нормальный никогда не делал ничего значимого в этом мире". - Jonathan, Stranger Things
Мы посмотрим на некоторые фрагменты кода с удивительным результатом и сделаем объяснение того, что происходит, чтобы лучше понять наш любимый язык программирования. Хотя это и странно, но нам это нравится!
Сценарий № 1: [‘1’, ‘7’, ‘11’].map(parseInt)
Давайте посмотрим на код для нашего первого сценария
['1', '7', '11'].map(parseInt);
Для чего вы ожидаете, что результат будет:
[1, 7, 11]
Тем не менее, здесь все немного не так, и реальный результат таков:
[1,NaN,3]
Поначалу это может показаться очень странным, но на самом деле оно имеет элегантное объяснение. Чтобы понять, что происходит, нам нужно понять две вовлеченные функции - map
и parseInt
.
карта()
map()
вызывает предоставленную функцию callback
один раз для каждого элемента в массиве по порядку и создает новый массив из результатов. callback
вызывается только для индексов массива, которым присвоены значения (включая неопределенные ).
Теперь callback
функция, указанная выше, получит некоторые конкретные параметры, давайте рассмотрим пример с ее выводом:
[1, 2, 3].map(console.log)
1 0 > (3) [1, 2, 3]
2 1 > (3) [1, 2, 3]
3 2 > (3) [1, 2, 3]
Как видно, функция map не только передавала значение элемента, но также индекс и копию полного массива на каждой итерации. Это важно и частично влияет на наш предыдущий результат.
ParseInt ()
Функция parseInt()
анализирует строковый аргумент и возвращает целое число , равное указанное основания (основание в математической системе счисления).
Так что теперь, по определению, parseInt(string [, radix])
ожидается два параметра: строка, которую мы хотим проанализировать, и основание.
Разгадай тайну
Теперь мы знаем достаточно о двух функциях, давайте попробуем понять, что происходит в нашем случае, мы начнем с нашего оригинального сценария и объясним его шаг за шагом:
['1', '7', '11'].map(parseInt);
Как мы знаем, callback
для функции map
будет получено 3 аргумента, поэтому давайте сделаем это:
['1', '7', '11'].map((currentValue, index, array) => parseInt(currentValue, index, array));
Начинаете понимать, что случилось? Когда мы добавляем аргументы, становится ясно, что parseInt
функция получает дополнительные параметры, а не только фактическое значение элемента в массиве, поэтому теперь мы можем проверить, что функция будет делать для каждой из этих комбинаций значений, но мы можем также игнорируйте параметр массива, так как он будет отброшен функцией parseInt
:
parseInt('1', 0)
1
parseInt('7', 1)
NaN
parseInt('11', 2)
3
Так что теперь объясняются значения, которые мы видели изначально, parseInt
результат функции изменяется параметром redix
, который определяет основу для преобразования.
Есть ли способ получить изначально ожидаемый результат?
Теперь узнав, как это работает, мы можем легко исправить наш скрипт и получить желаемый результат:
['1', '7', '11'].map((currentValue) => parseInt(currentValue));
> (3) [1, 7, 11]
Сценарий № 2: ('b' + 'a' + + 'a' + 'a'). ToLowerCase () === 'banana'
Возможно, вы думаете, что приведенное выше выражение ложно, в конце концов, в строке, которую мы строим в левой части выражения, нет буквы 'n', или нет? Давайте разберемся:
('b'+'a'+ + 'a' + 'a').toLowerCase() === 'banana'
true
Хорошо, вы, наверное, уже поняли, что происходит, но если нет, позвольте мне быстро объяснить это здесь. Давайте сосредоточимся на левой стороне выражения, с правой стороны нет ничего странного, поверьте мне.
('b'+'a'+ + 'a' + 'a').toLowerCase()
"banana"
Интересно, что мы формируем слово «банан», поэтому здесь возникает проблема, давайте удалим преобразование в нижний регистр и посмотрим, что произойдет:
('b'+'a'+ + 'a' + 'a')
"baNaNa"
Бинго! Теперь мы нашли некоторое «N», и похоже, что мы действительно нашли внутреннюю строку NaN
, возможно, это происходит из + +
выражения, давайте притворимся об этом и посмотрим, что мы получим:
b + a + NaN + a + a
Не совсем хорошо, у нас есть дополнительные a
, поэтому давайте попробуем что-то еще:
+ + 'a'
NaN
Ааа, мы идем… сама + +
по себе операция не оценивается, но когда мы добавляем символ «а» в конце, все это входит в состав NaN
и теперь вписывается в наш код. Затем выражение NaN
объединяется в виде строки с остальным текстом, и мы, наконец, получаем banana
. Довольно странно!
Сценарий № 3: даже не могу назвать это
(![] + [])[+[]] +
(![] + [])[+!+[]] +
([![]] + [][[]])[+!+[] + [+[]]] +
(![] + [])[!+[] + !+[]] === 'fail'
Что в мире? Как связка скобок образует слово неудачно? И поверьте мне, JS не ошибается, мы на самом деле получаем строку fail
в качестве вывода.
Давайте попробуем объяснить это, в этой связке есть несколько вещей, которые образуют шаблон:
(![] + [])
Этот шаблон вычисляется в строку false
, что странно, но это свойство языка, оказывается, что false + [] === 'false'
это преобразование связано с тем, как JS внутренне отображает внутренние вызовы, мы не будем вдаваться в подробности, почему именно это случается.
После того, как вы сформируете строку, false
все остальное легко, просто ищите позиции букв, которые вам нужны, за исключением одного случая, буквы, i
которая не является частью слова false
.
Для этого оригинальное выражение немного изменилось, давайте посмотрим на него, ([![]] + [][[]]),
которое оценивает строку falseundefined
. Таким образом, мы в основном устанавливаем неопределенное значение и объединяем его со false
строкой, которую мы знаем, как получить, а остальное - история.
Любить это до сих пор? Давайте сделаем еще немного.
Сценарий № 4: чтобы быть правдой или быть правдой, вот в чем вопрос
Чтобы быть правдой или быть правдой, вот в чем вопрос. Быть ложным или ложным - вот в чем вопрос.
Что такое правдивость и ложь? и почему они отличаются от истинного или ложного?
Каждое значение в JavaScript как его собственное логическое значение (истина / ложь), эти значения используются в операциях, где логическое значение ожидается, но не дано. Скорее всего, вы хотя бы раз сделали что-то вроде этого:
const array = [];
if (array) {
console.log('Truthy!');
}
В приведенном выше коде array
не является логическим значением, даже если значение «истинно», и выражение приведет к выполнению console.log
.
Как я узнаю, что является правдой, а что ложью?
Все, что не является ложью, является правдой. Страшное объяснение? достаточно справедливо, давайте рассмотрим это дальше.
Falsy - это значения с логическим наследованием false
, такие как:
- 0
- -0
- 0n
- '’ или "”
- null
- undefined
- NaN
Все остальное было бы правдой.
Сценарий № 5: равенство массивов
Некоторые вещи в JS просто странные, это то, как язык является дизайном, и мы принимаем его таким, какой он есть. Давайте посмотрим на некоторые странные равенства массивов:
[] == '' // -> true
[] == 0 // -> true
[''] == '' // -> true
[0] == 0 // -> true
[0] == '' // -> false
[''] == 0 // -> true
[null] == '' // true
[null] == 0 // true
[undefined] == '' // true
[undefined] == 0 // true
[[]] == 0 // true
[[]] == '' // true
[[[[[[]]]]]] == '' // true
[[[[[[]]]]]] == 0 // true
[[[[[[ null ]]]]]] == 0 // true
[[[[[[ null ]]]]]] == '' // true
[[[[[[ undefined ]]]]]] == 0 // true
[[[[[[ undefined ]]]]]] == '' // true
Если вас интересует почему? вы можете прочитать его в разделе 7.2.13 Сравнение абстрактного равенства спецификации. Хотя я должен предупредить вас, это не для нормальных людей.
Сценарий № 6: Математика есть Математика, если ...
В нашем реальном мире мы знаем, что математика - это математика, и мы знаем, как она работает, нас с детства учили, как добавлять числа, и что всегда, если вы сложите одинаковые числа, вы получите результат, верно? Ну ... для JavaScript это не всегда верно ... или вроде ... давайте посмотрим:
3 - 1 // -> 2
3 + 1 // -> 4
'3' - 1 // -> 2
'3' + 1 // -> '31'
'' + '' // -> ''
[] + [] // -> ''
{} + [] // -> 0
[] + {} // -> '[object Object]'
{} + {} // -> '[object Object][object Object]'
'222' - -'111' // -> 333
[4] * [4] // -> 16
[] * [] // -> 0
[4, 4] * [4, 4] // NaN
Сначала все началось хорошо, пока мы не добрались до:
'3' - 1 // -> 2
'3' + 1 // -> '31'
Когда мы вычитали, строка и число взаимодействовали как числа, но во время сложения оба действовали как строка, почему? Что ж ... он спроектирован таким образом, но есть простая таблица, которая поможет вам понять, что JavaScript будет делать в каждом случае:
Number + Number -> addition
Boolean + Number -> addition
Boolean + Boolean -> addition
Number + String -> concatenation
String + Boolean -> concatenation
String + String -> concatenation
А как насчет других примеров? Методы A ToPrimitive
и ToString
неявно вызываются для []
и {}
перед добавлением. Узнайте больше о процессе оценки в спецификации:
Примечательно, что {} + []
здесь есть исключение. Причина, по которой он отличается от [] + {}
этого, заключается в том, что без круглых скобок он интерпретируется как кодовый блок, а затем унарный +, преобразуя его []
в число. Он видит следующее:
{
// a code block here
}
+[]; // -> 0
Чтобы получить такой же вывод, как [] + {}
мы можем заключить его в скобки.
({} + []); // -> [object Object]
Вывод
Надеюсь, вам понравится этот пост так же, как мне понравилось его писать. JavaScript - удивительный язык, полный хитростей и странностей, и я надеюсь, что эта статья внесет ясность в некоторые из этих интересных тем, и в следующий раз, когда вы столкнетесь с чем-то вроде этого, вы будете знать, что именно происходит.
Есть еще ситуации, в которых JS может быть очень странным, и я, возможно, сделаю больше подобных постов в будущем, если вам всем это понравится.
Спасибо за прочтение!
Не нашли ответ на свой вопрос? Возможно, вы найдете решение проблемы на нашем канале в Youtube! Здесь мы собрали небольшие, но эффективные инструкции. Смотрите и подписывайтесь на наш youtube-канал!
Добавить комментарий