Primary tabs

Странные вещи, 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 все остальное легко, просто ищите позиции букв, которые вам нужны, за исключением одного случая, буквы, которая не является частью слова 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 может быть очень странным, и я, возможно, сделаю больше подобных постов в будущем, если вам всем это понравится.

Спасибо за прочтение!

Добавить комментарий

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.

Не нашли ответ на свой вопрос? Возможно, вы найдете решение проблемы на нашем канале в Youtube! Здесь мы собрали небольшие, но эффективные инструкции. Смотрите и подписывайтесь на наш youtube-канал!

Смотреть на Youtube