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

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!'";
Софья Пирогова

Софья Пирогова

Главный редактор / Автор статей