JavaScript: топ-7 ошибок в коде, которые легко пропустить

Эти ошибки совершают все: и новички, и мидлы, и тимлиды. В чем их уникальность? Что их объединяет? Судите сами ↓
Совершать ошибки нормально, а ошибки, о которых пойдет речь, совершаются случайно из-за невнимательности или при рефакторинге кода. Когда ты часами не можешь найти баг из-за которого лег прод, просто спокойно промониторь свой документ на такие вещи, как...
Проблемы наименования переменных
Название аргумента в функции совпадает с названием переменной
Эта ошибка означает, что код использует одно и то же имя для аргумента функции и для переменной в области видимости, в которой эта функция определена или вызвана. Рассмотрим два примера:
- Неосознанное или непреднамеренное использование одинаковых имен. Например, если мы хотим написать функцию, которая принимает число в качестве аргумента и увеличивает его на единицу, то мы можем написать так:
// Создаем переменную в глобальной области видимости
let x = 10;
function increment(x) { // Аргумент функции с тем же именем
x = x + 1; // Изменяем значение аргумента
return x; // Возвращаем новое значение
}
console.log(increment(x)); // 11
console.log(x); // 10
Такой подход может привести к тому, что мы не сможем изменить значение переменной x в глобальной области видимости, так как функция increment использует свой собственный аргумент x, который скрывает переменную x из внешней области видимости. Поэтому, когда мы вызываем функцию increment с переменной x в качестве аргумента, мы передаем копию значения переменной x, а не ссылку на саму переменную.
Для того, чтобы решить эту проблему, нужно использовать разные имена для аргумента функции и для переменной в области видимости:
let x = 10; // Переменная в глобальной области видимости
function increment(y) { // Аргумент функции с другим именем
y = y + 1; // Изменяем значение аргумента
return y; // Возвращаем новое значение
}
console.log(increment(x)); // Выведет: 11
console.log(x); // Выведет: 10
В этом случае мы не создаем конфликт между именами аргумента функции и переменной в области видимости и не нарушаем принципы чистого кода.
- Осознанное или преднамеренное использование одинаковых имен. Давайте напишем функцию, которая принимает объект в качестве аргумента и изменяет его свойства:
// Создаем объект в глобальной области видимости
let person = {
name: "Alice",
age: 25,
};
function changePerson(person) {// Аргумент функции с тем же именем
person.name = "Bob"; // Изменяем свойство name объекта person
person.age = 30; // Изменяем свойство age объекта person
}
changePerson(person); // Вызываем функцию с объектом person в качестве аргумента
console.log(person); // Получаем:{name: "Bob", age: 30}
В этом случае возможно изменить значение объекта person в глобальной области видимости, так как функция changePerson использует свой собственный аргумент person, который ссылается на тот же объект, что и переменная person из внешней области видимости. Поэтому, когда мы вызываем функцию changePerson с объектом person в качестве аргумента, мы передаем ссылку на этот объект, а не копию его значения. Но, это может быть не то, что мы хотели, так как мы изменяем исходный объект person, а не создаем новый объект с измененными свойствами. Для того, чтобы решить эту проблему, можно использовать разные имена для аргумента функции и для переменной в области видимости или создавать копию объекта перед его изменением:
// Создаем объект в глобальной области видимости
const person = {
name: "Alice",
age: 25,
};
function changePerson(obj) {
// Аргумент функции с другим именем
const newPerson = {...obj}; // Создаем копию объекта obj
newPerson.name = "Bob"; // Изменяем свойство name нового объекта newPerson
newPerson.age = 30; // Изменяем свойство age нового объекта newPerson
return newPerson; // Возвращаем новый объект
}
// Вызываем функцию с объектом person в качестве аргумента
// Присваиваем результат переменной changedPerson
const changedPerson = changePerson(person);
console.log(person); // {name: "Alice", age: 25}
console.log(changedPerson); // {name: "Bob", age: 30}
В этом случае мы не создаем конфликт между именами аргумента функции и переменной в области видимости и не изменяем исходный объект person, а создаем новый объект с измененными свойствами.

Большие и маленькие буквы в переменных
Эта ошибка означает, что код использует разный регистр букв для одной и той же переменной или функции, что приводит к тому, что они не распознаются или считаются разными. Например, если мы объявили переменную name, а потом пытаемся использовать переменную Name, то мы получим ошибку, так как Name не существует, например:
var name = "Alice"; // объявляем переменную name
console.log(Name);
// Выведет ошибку: ReferenceError: Name is not defined
В этом примере мы не сможем использовать переменную Name, так как мы ошиблись и написали большую букву N вместо маленькой.
Путаница в областях видимости
Область видимости — это часть кода, в которой определена или доступна переменная или функция. Самая частая ошибка новичка — путать глобальную и локальную области видимости. Это может произойти при:
- Отсутствии ключевых слов для объявления переменных или функций. Обычно, если мы хотим объявить переменную внутри функции и использовать ее только внутри этой функции, то мы пишем так:
function foo() {
const x = 10; // Объявляем переменную x с помощью const внутри функции foo
console.log(x); // 10
}
foo(); // Вызываем функцию foo
console.log(x); // ReferenceError: x is not defined
Но иногда программист может забыть указать ключевое слово и код выглядит немного иначе:
function foo() {
x = 10; // Объявляем переменную x без ключевого слова const внутри функции foo
console.log(x); // 10
}
foo(); // Вызываем функцию foo
console.log(x); // 10
В этом случае переменная x, которая объявлена без ключевого слова const внутри функции foo, доступна за пределами этой функции и имеет значение 10 после ее вызова. Такая ситуация может привести к тому, что эта переменная будет конфликтовать с другими переменными с тем же именем в других местах кода или подключенных скриптах.
- Не знании, как действует то или иное ключевое слово. Например, хоть var и let выглядят похожими, но в условиях, циклах и функциях ведут себя по разному:
// Условия
if (true) {
var x = "Меня можно увидеть";
let y = "Я в блоке";
}
console.log(x); // Меня можно увидеть
console.log(y); // ReferenceError: y is not defined
// Цикл c let
for (let i = 0; i < 3; i++) {}
console.log(i); // ReferenceError: i is not defined
// Цикл c var
for (var i = 0; i < 3; i++) {}
console.log(i); // 3
// Функция
function foo() {
var a = 20;
let b = 10;
}
console.log(a); // ReferenceError: a is not defined
console.log(b); // ReferenceError: b is not defined
Поэтому во время изучения программирования постарайтесь глубже изучить тему области видимости и сами поэкспериментируйте с кодом!
Простой пример, как работают области видимости
Представьте, что сначала вы объявляете переменную x в глобальной области видимости с помощью ключевого слова var, чтобы изменить ее значение внутри функции foo:
var x = 10; // Объявляем переменную x с помощью var в глобальной области видимости
function foo() {
x = 20; // Изменяем значение переменной x внутри функции foo
console.log(x); // 20
}
foo(); // Вызываем функцию foo
console.log(x); // Получаем 20
Но, потом вы хотите создать дополнительную переменную x и в локальной области видимости, чтобы не изменять ее значение в глобальной области видимости. В этом случае можно использовать ключевое слово var для объявления переменной x внутри функции foo:
var x = 10; // Объявляем переменную x с помощью var в глобальной области видимости
function foo() {
var x = 20; // Объявляем новую локальную переменную x с помощью var внутри функции foo
console.log(x); // 20
}
foo(); // Вызываем функцию foo
console.log(x); // Получаем 10
В этом случае переменная x, которая объявлена с помощью var внутри функции foo, не влияет на значение переменной x, которая объявлена с помощью var в глобальной области видимости.

Незакрытые скобки
Незакрытые скобки — это еще одна частая и типичная ошибка в JS у начинающих. Программисты используют скобки для группировки выражений, условий, параметров или блоков кода, но иногда могут забывать закрыть одну или несколько скобок в нужном месте из-за:
- Невнимательности или спешки при написании кода. Например, если мы хотим написать функцию, которая принимает два числа в качестве аргументов и возвращает их сумму, то мы можем написать так:
function sum(a, b) {
return a + b;
}
Однако, если мы забудем закрыть одну из круглых скобок при объявлении функции или при ее вызове, то мы получим ошибку. Например:
function sum(a, b {
return a + b;
}
console.log(sum(1, 2));// SyntaxError: Unexpected token '{'
Также мы получим ошибку, если переборщим со скобками:
function sum(a, b)) {
return a + b;
}
console.log(sum(1, 2)); //SyntaxError: Unexpected token ')'
- Неправильного понимания приоритета или ассоциативности операторов. Например, если мы хотим написать условие, которое проверяет, является ли значение переменной x больше 10 и меньше 20, то мы можем написать так:
const x = 15;
if (x > 10 && x < 20) {
console.log("Я люблю пельмени")
} else {
console.log("Я все-таки не люблю пельмени")
}
// Выведет: Я люблю пельмени
Еще мы поставим другой приоритет скобок, то соответственно получим не совсем нужный нам результат:
const x = 25;
if (x > (10 && x < 20)) {
console.log("Я люблю пельмени")
} else {
console.log("Я все-таки не люблю пельмени")
}
// Выведет: Я люблю пельмени
Без разницы поставите вы в переменную x 15 или 25, 100 или 1, вы все равно будете любить пельмени, так как оператор && имеет более высокий приоритет, чем оператор >, и будет вычислен первым. Поэтому условие x > (10 && x < 20) будет в нашем случае эквивалентно условию x > (x < 20), что не соответствует нашей задумке.
Точка с запятой нужна не всегда
Программисты в коде использует точку с запятой для разделения инструкций. Но иногда не ставят ее в нужных местах или ставит ее в лишних местах. Почему так происходит? ↓
- Неправильное понимание правил ASI. ASI (automatic semicolon insertion) — механизм, который автоматически расставляет точки с запятой. Например, если мы хотим написать функцию, которая возвращает объект с двумя свойствами name и age, то мы можем написать так:
function createPerson(name, age) {
return {
name: name,
age: age,
};
}
Но если мы сделаем перенос строки после слова return, то JavaScript попытается автоматически вставить точку с запятой за нас, но сделает это не там, где мы хотели, например:
function createPerson(name, age) {
return;
{
name: name,
age: age,
}
}
В этом случае произойдет ошибка SyntaxError: Unexpected token ':'.
- Ошибочное использование точки с запятой в циклах или условиях. Например, если мы хотим написать цикл for, который выводит в консоль числа от 1 до 10, то мы можем написать так:
for (var i = 1; i <= 10; i++) {
console.log(i);
}
Однако, если мы поставим точку с запятой после условия цикла for, то мы получим неправильный результат:
for (var i = 1; i <= 10; i++); {
console.log(i); // 11
}
В этом случае цикл for не выполнит блок кода в фигурных скобках для каждого значения i от 1 до 10, а выполнит его только один раз для значения i равного 11 после завершения цикла.
Кривой перенос строки
Начнем с того, что в строковых литералах для переноса строки используется \n:
const text = "Hello\nWorld"
console.log(text)
// Hello
// World
В шаблонных строках можно просто перейти на новую строку:
const text = `
Hello
World`
Но в обычных строках это не работает:
var text = "Hello
World"
// SyntaxError: Invalid or unexpected token
Прерывание выражения return
Перенос строки в выражении return — еще одна распространенная ошибка в JS. Обычно это случается в двух случаях:
- При переносе строки после return. В этом случае программа прервет выражение и вернёт undefined.
function double(num) {
return
num * 2;
}
- При переносе свойства объекта на строчку ниже. Тут код разделяется на два выражения, что приведёт к ошибке:
function getName(obj) {
return obj
["name"];
}
Лишняя запятая
В большинстве современных сред можно использовать замыкающие запятые (лишние запятые в конце определения объекта или массива), например:
// Лишняя запятая после age в объекте
const user = {
name: "John",
age: 30,
};
// Лишняя запятая после 3 в массиве
const numbers = [1, 2, 3,];
Но в некоторых средах выполнения такая запятая вызовет ошибку, что может привести к непредсказуемому поведению. Например, в IE8 и ниже будет ошибка, а в Firefox длина массива numbers будет 4 вместо 3. Кстати, если вы поставите такую запятую даже в современной среде в rest-параметрах, то получите:
function simple(...x,) {};
// SyntaxError: Rest parameter must be last formal parameter
let [a, ...b,] = ["А", "Б", "В", "Г", "Д"];
// SyntaxError: Rest element must be last element
Заковыка с кавычками
С кавычками существует две проблемы, либо они не совпадают, либо совпадают. Сейчас поясним:
- Несовпадающие кавычки. Такие кавычки получаются, когда строка начинается с одного вида кавычек, а заканчивается другим:
let name = "Alice'; // SyntaxError: Invalid or unexpected token
- Повтор одинаковых кавычек. Если вы вложите без специальных символов в строку другую строку с тем же самым типом кавычек, то получите небольшой конфуз:
let book = "Булгаков "Мастер и Маргарита""; // SyntaxError: Unexpected identifier 'Мастер'
Чтобы избежать ошибок с использованием кавычек, следуйте таким правилам:
- Строка должна начинаться и заканчиваться кавычками одного типа.
- Чтобы использовать такой же тип кавычек внутри строки, экранируем их обратным слешем `\`:
let quote = "She said: \"Hello, world!\"";
- Или используем разные типы кавычек для внешней и внутренней строки:
let quote = "She said: 'Hello, world!'";