Learn JavaScript

Частые ошибки новичков: подборка примеров

Даже маленькая ошибка в JavaScript часто выглядит как “все сломалось”. Ниже - самые типовые ситуации, которые встречаются в первых проектах. У каждой ошибки есть короткий пример и понятное исправление.

1) Переменная не объявлена: ReferenceError

Проблема: обращение к имени, которого нет.

console.log(total);

Исправление: объявить переменную или проверить имя.

let total = 0;
console.log(total);

2) Путаница == и ===

Проблема: == приводит типы и может дать неожиданные совпадения.

console.log(0 == false);
console.log("" == 0);

Исправление: для предсказуемости использовать ===.

console.log(0 === false);
console.log("" === 0);

3) Строка вместо числа (особенно из input)

Проблема: значения из формы часто строки.

const a = "2";
const b = "3";
console.log(a + b);

Исправление: приводить к числу.

const a = Number("2");
const b = Number("3");
console.log(a + b);

4) NaN: число “не получилось”

Проблема: Number("abc") дает NaN, и дальше вычисления тоже станут NaN.

const value = Number("abc");
console.log(value);
console.log(value + 10);

Исправление: проверять Number.isNaN.

const value = Number("abc");

if (Number.isNaN(value)) {
  console.log("Bad number");
} else {
  console.log(value + 10);
}

5) Неверное условие: = вместо ===

Проблема: присваивание внутри условия.

let status = "ok";

if (status = "fail") {
  console.log("error");
}

Этот код присваивает "fail" и условие становится истинным (строка truthy).

Исправление: сравнение.

let status = "ok";

if (status === "fail") {
  console.log("error");
}

6) Off-by-one в циклах: границы

Проблема: неправильное условие, выход за пределы.

const items = ["a", "b", "c"];

for (let i = 0; i <= items.length; i++) {
  console.log(items[i]);
}

На последнем шаге items[i] будет undefined.

Исправление: i < items.length.

const items = ["a", "b", "c"];

for (let i = 0; i < items.length; i++) {
  console.log(items[i]);
}

7) Бесконечный цикл: забыли шаг

Проблема: счетчик не меняется.

let i = 0;

while (i < 3) {
  console.log(i);
}

Исправление: менять i.

let i = 0;

while (i < 3) {
  console.log(i);
  i++;
}

8) Мутации массива: “исчезло само”

Проблема: методы вроде sort, reverse, splice меняют исходный массив.

const nums = [10, 2, 30];
const sorted = nums.sort((a, b) => a - b);

console.log(nums);
console.log(sorted);

И nums, и sorted будут отсортированы, потому что это один и тот же измененный массив.

Исправление: копировать перед изменением.

const nums = [10, 2, 30];
const sorted = [...nums].sort((a, b) => a - b);

console.log(nums);
console.log(sorted);

9) Ошибка с объектами: сравнение по ссылке

Проблема: объекты сравниваются по ссылке, не по содержимому.

console.log({ a: 1 } === { a: 1 });

Исправление: сравнивать конкретные поля или писать свою проверку.

const x = { a: 1 };
const y = { a: 1 };

console.log(x.a === y.a);

10) undefined из объекта: несуществующее свойство

Проблема: опечатка или нет поля.

const user = { name: "Ann" };
console.log(user.age);

Исправление: проверка или безопасный доступ.

const user = { name: "Ann" };

if ("age" in user) {
  console.log(user.age);
} else {
  console.log("No age");
}

11) DOM: querySelector вернул null

Проблема: селектор не нашел элемент, а код сразу читает свойства.

const title = document.querySelector(".title");
console.log(title.textContent);

Исправление: проверять null и корректность селектора.

const title = document.querySelector(".title");

if (title) {
  console.log(title.textContent);
}

12) innerHTML и пользовательские данные

Проблема: вставка строки, которая может содержать HTML.

const el = document.querySelector("#out");
const name = "<img src=x onerror=alert(1)>";
el.innerHTML = name;

Исправление: для текста использовать textContent.

const el = document.querySelector("#out");
const name = "<img src=x onerror=alert(1)>";
el.textContent = name;

13) addEventListener: вызвать функцию вместо передать

Проблема: обработчик вызывается сразу.

function onClick() {
  console.log("click");
}

const btn = document.querySelector("#btn");
btn.addEventListener("click", onClick());

Исправление: передать функцию без скобок.

function onClick() {
  console.log("click");
}

const btn = document.querySelector("#btn");
btn.addEventListener("click", onClick);

14) Асинхронность: забыли await

Проблема: вместо данных получается Promise.

async function load() {
  const data = fetch("/api/data").then((r) => r.json());
  console.log(data);
}

load();

Исправление: поставить await.

async function load() {
  const data = await fetch("/api/data").then((r) => r.json());
  console.log(data);
}

load();

15) fetch: считать, что 404 - это ошибка промиса

Проблема: fetch отклоняется (reject) в основном на сетевых ошибках. HTTP 404 - это обычный ответ, но response.ok будет false.

async function loadJSON(url) {
  const response = await fetch(url);
  return response.json();
}

Исправление: проверять ok.

async function loadJSON(url) {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error("HTTP " + response.status);
  }

  return response.json();
}

16) try/catch не ловит ошибку без await

Проблема: ошибка в промисе не попадет в catch, если промис не awaited внутри try.

async function run() {
  try {
    fetch("/api/data").then(() => {
      throw new Error("fail");
    });
  } catch (e) {
    console.log("caught");
  }
}

run();

Исправление: использовать await или ловить catch у промиса.

async function run() {
  try {
    await fetch("/api/data").then(() => {
      throw new Error("fail");
    });
  } catch (e) {
    console.log("caught");
  }
}

run();

17) import/export: перепутать default и named

Проблема: default экспорт импортируется без фигурных скобок.

sum.js

export default function sum(a, b) {
  return a + b;
}

Неправильно:

import { sum } from "./sum.js";

Правильно:

import sum from "./sum.js";

18) “Склейка” логики и UI в одном месте

Проблема: обработчик делает все подряд: запрос, преобразование, отрисовку, сохранение, валидацию.

btn.addEventListener("click", async () => {
  const response = await fetch("/api/items");
  const items = await response.json();

  const filtered = items.filter((x) => x.active);
  document.querySelector("#out").textContent = String(filtered.length);
  localStorage.setItem("lastCount", String(filtered.length));
});

Исправление: выносить по смыслу в функции и файлы (data/logic/ui), а в обработчике оставлять склейку.

async function loadItems() {
  const response = await fetch("/api/items");

  if (!response.ok) {
    throw new Error("HTTP " + response.status);
  }

  return response.json();
}

function countActive(items) {
  return items.filter((x) => x.active).length;
}

function renderCount(root, count) {
  root.textContent = String(count);
}

btn.addEventListener("click", async () => {
  try {
    const items = await loadItems();
    const count = countActive(items);

    renderCount(document.querySelector("#out"), count);
    localStorage.setItem("lastCount", String(count));
  } catch (e) {
    console.log(e.message);
  }
});

Что стоит запомнить

  • Большая часть ошибок новичков - это типы (строка вместо числа), границы циклов и null из DOM.
  • === дает предсказуемые сравнения.
  • Методы sort/splice/reverse мутируют массив.
  • В fetch важно проверять response.ok.
  • Чтение сообщения ошибки и stack trace обычно дает ответ быстрее, чем догадки.