Программирование с ИИ
ENRU
9.1

Читаем JavaScript

шесть мест, где ломается питоновская интуиция

Что это

JavaScript похож на Python ровно настолько, чтобы вводить в заблуждение. Циклы, функции, условия выглядят знакомо — а потом код делает не то, и непонятно почему.

В статье шесть мест, где интуиция подводит. Всё ниже — настоящий вывод node, а не пересказ документации.

Как выглядит в коде

1. Сортировка чисел сортирует не числа

[10, 9, 1, 100].sort()
измерено
[ 1, 10, 100, 9 ]

По умолчанию sort превращает элементы в строки и сравнивает их как текст. Для чисел нужна функция сравнения:

[10, 9, 1, 100].sort((a, b) => a - b)     // [ 1, 9, 10, 100 ]

Ошибка тихая: на числах от 1 до 9 она незаметна, на двузначных — ломается.

2. Два вида равенства

измерено
'1' ==  1   ->  true
'1' === 1   ->  false
null ==  undefined   ->  true
null === undefined   ->  false

== приводит типы перед сравнением, === — нет. Правило простое: всегда ===. Единственное оправданное исключение — x == null, которое разом проверяет и null, и undefined.

3. Истинность не та, к которой ты привык

измерено
[]        -> истина        (в Python пустой список ложен!)
{}        -> истина
""        -> ложь
"0"       -> истина
0         -> ложь
NaN       -> ложь
null      -> ложь
undefined -> ложь

Первая строка ломает больше всего кода. Проверка «есть ли элементы» через if (items) в JavaScript всегда истинна, даже для пустого массива. Нужно if (items.length).

4. Плюс склеивает, минус вычитает

измерено
1 + '1'   ->  '11'      (склеил строки)
1 - '1'   ->  0         (превратил в числа)

Отсюда классика: данные пришли из формы или JSON строками, и вместо суммы получается склейка. '10' + 5 даёт '105'.

5. Два вида «ничего»

Что значит
undefinedзначения не было: нет такого ключа, функция ничего не вернула
nullзначение есть, и оно — «ничего»

Обращение к несуществующему ключу не падает:

измерено
user.email              ->  undefined
user.email?.length      ->  undefined
user.email ?? 'не указан'  ->  'не указан'

Это противоположность Python, где такое даёт KeyError. Удобно и опасно одновременно: ошибка не сообщает о себе, а всплывает дальше — в виде Cannot read properties of undefined (статья 8.4).

Два оператора, которые встречаются постоянно:

  • ?. — «если слева не null/undefined, иди дальше, иначе верни undefined»;
  • ?? — «возьми левое, а если оно null/undefined, возьми правое».

?? отличается от || тем, что не считает 0 и "" пустотой. Для чисел это принципиально: temp || 20 превратит ноль градусов в двадцать, а temp ?? 20 — нет.

6. const не делает объект неизменяемым

const cities = ["Oslo"];
cities.push("Bergen");      // можно
cities = [];                // нельзя

const запрещает переприсваивать переменную, но не менять содержимое. То же самое, что ссылки в Python (статья 2.4).

Как сказать ИИ

Запрос
Перепиши этот код на строгие сравнения: везде === вместо ==,
проверки массивов через .length, ?? вместо || там,
где значение может быть 0 или пустой строкой.
Поведение не меняй.
Запрос
Этот код пришёл из Python. Что здесь будет работать иначе
из-за особенностей JavaScript? Только список различий.
Запрос
Числа приходят из JSON строками. Где в этом коде может
получиться склейка вместо сложения?

На что смотреть

== в сгенерированном коде. Почти всегда должно быть ===.

sort() без функции сравнения на числах.

if (array) вместо if (array.length). Условие всегда истинно.

|| для значений по умолчанию. Ломается на 0 и "". Нужен ??.

var. Устаревшее объявление с неожиданной областью видимости. В новом коде только let и const (статья 3.1, причина 3).

Числа из строк без преобразования. Number(x) или parseInt(x, 10) — и всегда с основанием 10.

Шпаргалка

  • sort() по умолчанию сортирует как строки: [1, 10, 100, 9].

  • Всегда ===. Исключение — x == null для обеих пустот сразу.

  • Пустой массив — истина. Проверяй .length.

  • + со строкой склеивает, - преобразует в числа.

  • ?. безопасный доступ, ?? значение по умолчанию (не путать с ||).

  • const запрещает переприсваивание, но не изменение содержимого.