15 распространенных ошибок JavaScript для начинающих

Я обучал JavaScript сотни людей очно и тысячи онлайн. За время своего преподавания я видел, как начинающие разработчики JavaScript совершают одни и те же ошибки снова и снова. Вот 15 самых распространенных ошибок начинающих JavaScript, которые я видел.

Полный исходный код и примеры вы найдете в этом реплайте.

1. Невозврат из функции

Если вы когда-нибудь вызывали функцию и получали в ответ undefined, вы, вероятно, уже сталкивались с этой ошибкой. Функции в JavaScript по умолчанию возвращают значение undefined, что означает, что если вы не вернете ничего явно с помощью ключевого слова return, результатом будет undefined.

Поэтому вместо этого.

const getAddedValue = (a, b) => {
 a + b;
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Убедитесь, что вы действительно возвращаете результат.

const getAddedValue = (a, b) => {
  return a + b;
}
Войти в полноэкранный режим Выйти из полноэкранного режима

2. Загрузка сценариев JavaScript в HTML до загрузки DOM

Это единственный пример, в котором мы немного коснемся того, как JavaScript взаимодействует с HTML и DOM. Ключевым моментом в их взаимодействии является то, что время очень важно. Если вы хотите сослаться на элемент DOM в своем JavaScript, этот элемент DOM должен быть уже загружен на страницу. Давайте рассмотрим пример.

Допустим, у вас есть HTML, который также импортирует файл JavaScript под названием script.js. Обратите внимание, что файл JavaScript импортируется перед элементом h1 с id header.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JavaScript Mistake Demo</title>
</head>
<body>
 <script src="script.js"></script>
 <h1 id="header">Hello world</h1>
</body>

</html>
Вход в полноэкранный режим Выход из полноэкранного режима

Затем в своем JavaScript вы хотите получить ссылку на элемент header и обновить его текст.

const header = document.getElementById('header');
header.innerText = "James is cool!";
Войдите в полноэкранный режим Выйти из полноэкранного режима

В этом случае вы получите ошибку: Cannot set properties of null (setting 'innerText'). Это происходит потому, что JavaScript пытается получить ссылку на элемент DOM, который еще не был загружен на страницу.

Чтобы исправить это, обычно импортируйте JavaScript в нижнюю часть HTML, после загрузки всех элементов HTML.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>JavaScript Mistake Demo</title>
  </head>
  <body>
   <h1 id="header">Hello world</h1>
   <script src="script.js"></script>
  </body>
</html>
Вход в полноэкранный режим Выйти из полноэкранного режима

Таким образом, файл JavaScript загружается после того, как элемент header уже загружен.

Важно отметить, что существуют и другие варианты импорта файлов JavaScript в HTML. То, что мы показали, является базовым примером для начинающих, но существуют дополнительные ключевые слова, которые вы можете изучить более подробно.

  • Атрибут async сценария HTML
  • Атрибут defer сценария HTML

3. Переназначение переменных Const

Это настолько распространенная ошибка, что она продолжает случаться со мной почти ежедневно, так что не расстраивайтесь, если это произойдет с вами. Начиная с ES6 (версия JavaScript, выпущенная в 2015 году), существует два основных способа объявления переменных: const и let. Эти два способа, по большей части, заменили использование var в современном JavaScript.

Разница между ними в том, что вы не можете переназначать переменные const, а let — можете. Вот пример кода.

const count = 0;
for (let i = 0; i < 10; i++) {
  count = count + i;
}
console.log(count);
Вход в полноэкранный режим Выйти из полноэкранного режима

В этом случае вы получите ошибку: Assignment to constant variable. Если вам нужно переназначить переменную, обязательно используйте let.

let count = 0;
for (let i = 0; i < 10; i++) {
  count = count + i;
}
console.log(count);
Вход в полноэкранный режим Выход из полноэкранного режима

Подробнее о const vs let vs var читайте в этой статье с freeCodeCamp.

4. Непонимание области видимости переменных

Область видимости переменных — сложная концепция для начинающих разработчиков, особенно в JavaScript. Одна из распространенных проблем, которую я вижу у начинающих разработчиков, — это определение переменной внутри функции и ожидание, что к этой переменной можно будет получить доступ за пределами функции, в которой она определена.

Это довольно общая проблема, с которой я сталкиваюсь. Давайте рассмотрим пример, более специфичный для JavaScript. Это возвращается к использованию const и let против var. Я упоминал, что var является более устаревшим способом объявления переменных, но вы все еще можете встретить его в документации и исходном коде. Поэтому важно понимать, чем отличается от него определение переменных.

Если вы определяете переменную с помощью var внутри цикла for, например, то эта переменная будет доступна и после цикла for. Вот быстрый пример.

for (let i = 0; i < 10; i++) {
  var count = 5;
}
console.log(count);
Вход в полноэкранный режим Выход из полноэкранного режима

Этот код действительно будет работать, хотя он неинтуитивно понятен. Значение count доступно после цикла for, в котором оно было определено. Однако это не работает для переменных const и let. Если они определены внутри цикла for, они доступны только внутри этого цикла.

for (let i = 0; i < 10; i++) {
  const count = 5;
}
console.log(count); //error: count is not defined
Вход в полноэкранный режим Выход из полноэкранного режима

Для более подробной информации вот еще одна замечательная статья с freeCodeCamp.

5. Плохо названные переменные

Плохо названные переменные делают код в миллион раз более трудным для чтения и выполнения. Эту ошибку я видел у всех начинающих разработчиков, с которыми мне приходилось работать. Использование имен типа thing1, thing2 и anotherThing не дает никакого контекста, что это за переменные. Это значительно усложняет отладку кода. Давайте рассмотрим пример.

const arr = ["James", "Jess", "Lily", "Sevi"];
let str = "";
for(let i = 0; i < arr.length; i++){
  const tmp = arr[i];
  str += tmp[0];
}
console.log(str);
Вход в полноэкранный режим Выход из полноэкранного режима

Переменные arr, str и tmp не дают никакого контекста, что это за переменные и что делает код. Вот пример, в котором используются соглашения об именовании, добавляющие гораздо больше контекста.

const names = ["James", "Jess", "Lily", "Sevi"];
let retVal = "";
for(let i = 0; i < arr.length; i++){
  const name = arr[i];
  retVal += name[0];
}
console.log(retVal);
Вход в полноэкранный режим Выход из полноэкранного режима

Один из самых простых советов — называть массивы во множественном числе по типу информации, которую они хранят. Например, массив имен должен называться names. Затем вы можете ссылаться на отдельные элементы внутри этого массива как name. Я часто встречаю массивы, названные в единственном числе, и это невероятно запутывает.

6. Слишком большие функции

Это довольно мета-ошибка, но она чрезвычайно распространена на ранних стадиях. Когда разработчики узнают о функциях в JavaScript, у них часто возникает желание написать одну функцию и свалить в нее весь код, который им нужен. Однако лучше начать думать о том, как разбить функции на более мелкие части кода, которые будут более читабельными, композиционными и т.д. Вместо примера кода, вот несколько дополнительных ссылок, которые вы можете прочитать для получения дополнительной информации.

  • https://medium.com/swlh/how-long-should-functions-be-how-do-we-measure-it-cccbdcd8374c
  • https://softwareengineering.stackexchange.com/questions/133404/what-is-the-ideal-length-of-a-method-for-you

7. Ненужные утверждения Else

Часто неправильно понимают, что оператор return внутри функции фактически останавливает выполнение этой функции. Другими словами, после возврата внутри функции никакой другой код внутри этой функции не выполняется. Из-за этого я часто вижу ненужные операторы else. Вот пример.

const isOdd = (num) => {
  if(num % 2 === 1) {
    return true
  }else {
    return false;
  }
}
Войти в полноэкранный режим Выход из полноэкранного режима

Поскольку у нас уже есть return в условии if, else не нужен. Мы упростим этот код, удалив условие else.

const isOdd = (num) => {
  if(num % 2 === 1) {
    return true
  }
  return false;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Вы даже можете пойти дальше и вернуть вычисленное выражение напрямую, поскольку num%2 === 1 возвращает булеву величину.

const isOdd = (num) => {
  return num % 2 === 1;
}
Вход в полноэкранный режим Выход из полноэкранного режима

8. Не замыкание циклов

Короткое замыкание — это еще один способ улучшить циклы for. Допустим, вы пишете функцию, в которой нужно определить, содержит ли массив чисел четное число. Вот пример того, как можно решить эту задачу.

const hasEvenNumber = (numbersArr) => {
  let retVal;
  for(let i =0; i< numbersArr.length; i++){
    if(numbersArr[i] % 2 === 0){
      retVal = true;
    }else {
      retVal = false;
    }
  }
  return retVal;
}
Войти в полноэкранный режим Выйти из полноэкранного режима

В этом случае мы итерируем каждое число и обновляем булеву функцию в зависимости от того, является ли число четным. К сожалению, здесь есть проблема. Допустим, первое число четное, а следующее — нечетное. Тогда булева функция обновится на true для четного числа, а затем на false для нечетного.

Это даст неверный ответ, поскольку вы просто хотите знать, есть ли в функции хотя бы одно четное число (она должна вернуть true). В данном случае, после того как вы увидите первое четное число, вы получите ответ. Вам больше не нужно искать. Здесь в игру вступает замыкание. После того как вы увидите одно четное число, вернитесь. Если вы так и не увидите ни одного, верните false в конце.

const hasEvenNumber = (numbersArr) => {
  for(let i =0; i< numbersArr.length; i++){
    if(numbersArr[i] % 2 === 0){
      return true;
    }
  }
  return false;
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Таким образом, ваша логика становится чище, и вы избегаете ненужного итерационного просмотра дополнительных элементов в массиве.

9. Двойные и тройные равенства

Это огромная тема, вызывающая путаницу в JavaScript. Для начала важно знать, что двойное равенство сравнивает два значения без учета их типов данных. С другой стороны, тройное равенство сравнивает два значения, принимая во внимание их типы данных.

Поскольку двойное равенство не учитывает типы данных двух значений, JavaScript должен иметь какой-то способ сравнить их. Для этого JavaScript тайно приводит (преобразует один тип данных в другой) каждое значение, чтобы их можно было сравнить. Это означает, что число и строка могут считаться дважды равными, но не трижды равными, поскольку они относятся к разным типам данных.

const jamesAge = "31";
const jessAge = 31;

console.log(jamesAge == jessAge); //equal
console.log(jamesAge === jessAge); //not equal
Вход в полноэкранный режим Выход из полноэкранного режима

Общий совет: используйте тройные равенства по умолчанию, если у вас нет особых причин использовать двойные равенства. Обычно это безопаснее и помогает избежать непредвиденных результатов. Более подробную информацию о том, как работают эти равенства, можно найти в документации MDN.

10. Неправильное сравнение объектов и примитивов

Другая похожая проблема, которую я вижу, — это попытка сравнить примитивы и объекты на равенство. В JavaScript существует семь примитивных значений.

  • число
  • строка
  • bigint
  • булево
  • неопределенный
  • символ
  • null

Все остальное представляется в виде объекта, причем на объекты и примитивы ссылаются по-разному. В JavaScript на примитивы ссылаются непосредственно по их значению. С другой стороны, объекты в более общем смысле ссылаются на пространство в памяти, где хранится значение (значения). Это приводит к некоторой путанице между объектами и примитивами. Рассмотрим следующий пример.

const name1 = "James";
const name2 = "James";
console.log(name1 === name2);
Вход в полноэкранный режим Выйти из полноэкранного режима

Поскольку эти две переменные являются примитивами с одинаковым значением, они считаются равными, но что если мы сравним два объекта с одинаковым свойством имени, например, так?

const person1 = {
  name:"James"
}
const person2 = {
  name:"James"
}
console.log(person1 === person2);
Войти в полноэкранный режим Выйти из полноэкранного режима

В этом случае эти два объекта не считаются равными. Это происходит потому, что две переменные на самом деле являются ссылками на разные «пространства в памяти». Хотя они выглядят одинаково, из-за этого они не считаются равными. Если бы вы хотели сравнить их равенство более корректно, вы могли бы сравнить их свойства name напрямую.

const person1 = {
  name:"James"
}
const person2 = {
  name:"James"
}
console.log(person1.name === person2.name);
Войти в полноэкранный режим Выйти из полноэкранного режима

11. Невозможно прочитать свойство Undefined

Пока я пишу это, я осознаю, что не обязательно упорядочивал их по частоте. Если бы я это сделал, то этот пункт был бы намного выше 🤣.

В любом случае, одна вещь, которую забывают делать начинающие (и опытные) разработчики — это проверка входных параметров функции. Если вы ожидаете получить объект, это еще не значит, что вы его получите. Давайте рассмотрим функцию, которая печатает свойство name объекта.

const printNamedGreeting = (person) => {
  console.log(person.name)
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Кажется, что все достаточно просто, но что произойдет, если кто-то вызовет эту функцию и ничего не передаст? Ну, вы получите ошибку cannot read property name of undefined. Итак, как улучшить этот код?

Ну, вам нужно будет проверить, что входные данные, которые вы получаете, соответствуют вашим ожиданиям. В данном случае одним из простых (хотя и неполных) решений будет проверка того, что входной параметр не является «falsy».

const printNamedGreeting = (person) => {
  if(!person){
    return console.log("Invalid person object")
  }
  console.log(person.name)
}
Вход в полноэкранный режим Выход из полноэкранного режима

В этом случае, если параметр person является «falsy», мы выводим ошибку. В противном случае мы продолжаем выводить свойство name.

Имейте в виду, что это быстрая и простая проверка, но в реальном приложении вам, вероятно, потребуется что-то более глубокое. Вы захотите проверить, что входные данные на самом деле являются объектом (а не строкой, например) и что они также имеют свойство name.

Наконец, прежде чем вы это скажете, да, вы можете использовать TypeScript для этого… я знаю! 😃

12. Мутация с массивами

Мутация — это интересная концепция в JavaScript. В общем случае мутация происходит, когда вы вызываете функцию для переменной, которая затем каким-то образом изменяет саму переменную. Например, когда вы вызываете функцию sort для массива, мутирует сам массив. Однако если вы вызываете функцию map на массиве, исходный массив остается нетронутым. Он не мутирует.

const names = ["Jess", "James", "Sevi", "Lily"];
const copiedNames = [...names]; 
const sortedNames = names.sort();
console.log(names); //["James", "Jess", "Lily", "Sevi"]
console.log(copiedNames); //["Jess", "James", "Sevi", "Lily"]
console.log(sortedNames); //["James", "Jess", "Lily", "Sevi"]
Вход в полноэкранный режим Выход из полноэкранного режима

В этом примере вызов функции names.sort() мутирует исходный массив names, то есть массивы names и sortedNames будут выглядеть одинаково. Однако массив copiedNames не пострадает, поскольку он был истинной копией исходного массива.

Однако, если вы вызовете функцию map() для этого массива, исходный массив names не пострадает.

const firstLettersArray = names.map( name => name[0]);
Вход в полноэкранный режим Выход из полноэкранного режима

Главный урок здесь — понять, как функции, которые вы вызываете, изменяют или не изменяют данные, с которыми вы работаете. Это должно быть указано в документации к той функции, которую вы вызываете.

13. Непонимание асинхронного кода

Асинхронная природа JavaScript — одна из самых сложных вещей для понимания начинающими разработчиками. Здесь не место для полного руководства по асинхронному JavaScript, поэтому я оставлю вам самый распространенный пример для начинающих, чтобы ввести вас в тему. В каком порядке будут выводиться эти сообщения журнала?

console.log("1");
setTimeout(() => {
  console.log("2")
}, 0)
console.log("3");
Вход в полноэкранный режим Выход из полноэкранного режима

Ответ может удивить, если вы новичок в этом деле. W3 Schools всегда является для меня любимым источником информации, поэтому вот их инструкция по началу работы с asynchronousJavaScript.

14. Не обрабатывать ошибки

Проблема многих учебников для начинающих заключается в том, что в них редко рассказывается о том, как обрабатывать ошибки в JavaScript. Вы видите только лучшие сценарии для примеров кода, но не код «что, если что-то пойдет не так». Это понятно, потому что в учебнике для начинающих можно рассказать лишь о многом. Однако в какой-то момент важно потратить некоторое время на изучение того, как правильно работать с ошибками.

Опять же, возможно, здесь не место для подробного руководства по обработке ошибок, поэтому я оставлю вам один совет. Когда вы пишете код, спрашивайте себя: «А что, если здесь что-то пойдет не так». Если вы будете держать эту мысль в голове в процессе обучения, вы будете в хорошем положении!

15. Не форматирование кода

Это еще один метапример, но он имеет большое значение. Как и в случае с именованием переменных выше, плохо отформатированный код невероятно трудно читать. Разработчики привыкли читать код в стандартизированном и отформатированном виде. Из-за этого код, который не отформатирован, становится просто невозможно прочитать.

Я неоднократно сталкивался с этим в процессе преподавания. Чтобы ответить на, казалось бы, простой вопрос, я пробираюсь назад по коду, пытаясь расшифровать происходящее. Урок заключается в том, чтобы всегда тратить немного времени и энергии на правильное форматирование кода с отступами, интервалами и т.д. Это значительно облегчит вам работу. Это значительно облегчит чтение как вам, так и всем остальным, кто может читать ваш код.

Оцените статью
devanswers.ru
Добавить комментарий