Синтаксис и семантика — Примеры
Пример 1 — Ноль, принятый за отсутствие
В объекте настроек хранится число повторных попыток. Код ниже печатает одно и то же сообщение и тогда, когда счётчик равен нулю, и тогда, когда его нет вовсе. Назовите правило, на которое здесь полагаются, а затем сделайте намерение явным.
const settings = { retries: 0, label: "" };
if (settings.retries) {
console.log("будем повторять");
} else {
console.log("попыток нет"); // выполняется, даже когда retries — настоящий 0
}
console.log(settings.label == false); // true — обе стороны приведены
console.log([] == false); // true — [] приводится к 0
Решение
if (settings.retries > 0) {
console.log("будем повторять");
}
if (Object.hasOwn(settings, "retries")) {
console.log("настройка задана");
}
console.log(settings.label === ""); // равенство без приведения
console.log([] === false); // false
Ложных значений шесть — false, 0, "", null, undefined и NaN, — поэтому проверка на истинность отвечает на вопрос «ложно ли это значение», а не «отсутствует ли оно»: законный 0 от ключа, которого нет, так не отличить. Перед сравнением == приводит типы — отсюда и равенство пустого массива false. Запись > выражает числовое намерение, Object.hasOwn — намерение о наличии, а === приведение прекращает. Правила существовали всегда; однако исходный код просто не говорил, на какой вопрос отвечает.
Пример 2 — Цикл, все колбэки которого видят последнее значение
В цикле собираются три функции, и каждая должна вывести собственный индекс. Все три выводят одно и то же число. Объясните, какую привязку они захватили, и исправьте цикл, не изменив его поведения.
const handlers = [];
for (var i = 0; i < 3; i++) {
handlers.push(() => console.log(i));
}
handlers.forEach((handler) => handler()); // 3 3 3
Решение
const handlers = [];
for (let i = 0; i < 3; i++) {
handlers.push(() => console.log(i)); // 0 1 2
}
handlers.forEach((handler) => handler());
Замыкание захватывает саму переменную, а не копию её значения, а var поднимается к объемлющей функции — поэтому все три стрелочные функции замыкаются на одну и ту же i, которая успевает дойти до 3, прежде чем запустится хоть одна из них. let блочный и на каждой итерации создаёт свежую привязку, так что каждый колбэк получает свою. То же правило объясняет, почему счётчик на замыкании хранит приватное состояние между вызовами: функция помнит место, где её написали, а не место, откуда её позвали.