Ошибки при изучении javascript

frontend logo

Хочешь проверить свои знания по фронтенду?

Подпишись на наш канал с тестами по HTML/CSS/JS в Telegram!

Решать задачи

×

Перевод статьи
«7 Mistakes That Keep You Behind».

Ошибки при изучении JavaScript

Не всем нравится процесс изучения
JavaScript, особенно он не по душе людям
нетерпеливым. Иногда дело доходит до
того, что человек начинает спрашивать
себя, правильный ли карьерный путь он
выбрал.

Так ведь никто и не обещал, что изучение
JavaScript будет легким и приятным, как
прогулка по парку!

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

1. Вы слишком торопитесь при
изучении концепций

Спешка

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

Перейти к следующей теме это большой
соблазн, я знаю. Это со всеми случается.
Но поверьте мне, если вы сделаете паузу
между темами и используете ее для
тренировки в написании кода, это очень
поможет вам в будущем: вам будет легче,
когда дело дойдет до создания проектов.
Зачастую все эти концепции лучше всего
усваиваются и надолго запоминаются
именно в процессе написания кода.

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

Продвигайтесь вперед только когда
чувствуете, что текущая тема вам уже
совершенно понятна и легка.

2. Вы не владеете своим кодом

Код

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

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

Именно по этой причине на многих собеседованиях по-прежнему дают задачи на программирование. Интервьюеры хотят увидеть, как вы мыслите, как вы применяете все эти концепции для решения распространенных задач. Можно использовать библиотеки компонентов для создания красивого пользовательского интерфейса, а lodash – для применения алгоритмов, но если возникнут проблемы, вы должны суметь применить свои знания основных концепций, чтобы разобраться с ними. Библиотеки не будут искать и устанавливать другую библиотеку или пакет для решения задачи. Для этого нужны вы.

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

3. Вы проводите слишком много
времени за обдумыванием и планированием

Лишнее обдумывание

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

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

Но это лишь игры вашего разума.

Не увлекайтесь процессом сбора 80
руководств, 6 книг и коллекции из 50
закладок в браузере. Не пытайтесь
охватить все это одновременно, следуя
своему желанию почувствовать себя
королем веб-разработки.

Если вы изучаете JavaScript, учитесь на
практике. Просто начните писать
что-нибудь. Используйте то, что изучили
за прошлый час, и напишите маленький
пример кода чисто для себя. Это принесет
вам больше пользы, чем марафонское
чтение всех собранных вами материалов.

4. Вы перегружаете себя лишней
информацией

Истощение от лишней информации

Когда вы проводите большую часть
своего времени, потребляя информацию
(вместо того чтобы почаще прерываться
на написание кода), вы наверняка забудете
большую ее часть и вам придется постоянно
возвращаться к пройденному материалу.

Проводить все время за учебой это
страшная ловушка, потому что при этом
вам кажется, что вы учитесь. Но что
будет через пару месяцев, когда вы
начнете все это забывать? Вы обнаружите,
что вы почему-то неспособны применить
на практике концепции и материалы,
изученные совсем недавно.

Могу посоветовать проводить за
написанием кода хотя бы 15 минут на каждый
час усвоения информации.

5. Вы сравниваете себя с более
опытными разработчиками

Сравнение себя с другими

Когда вы сравниваете себя с другими,
вы не думаете о том, как они достигли
такого уровня, вы думаете лишь о самом
этом уровне.

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

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

6. Изучение инструментов и
фреймворков, созданных поверх JavaScript

Изучение фреймворков вместо чистого языка

То есть, вам нужно изучить JavaScript, но
вместо этого вы сразу переходите к
изучению React/jQuery/Angular/Vue. Но что будет,
когда выйдет новый революционный
инструмент и вам придется переключиться
на него? Вы будете вынуждены обращаться
к кому-нибудь, чтобы разобраться в
различных тонкостях, и надеяться, что
этот человек поделится с вами своими
знаниями, потому что он изучил JavaScript, а
вы – нет!

Если вы не приложили достаточно усилий
для изучения «ванильного» JavaScript, я
настоятельно советую действительно
изучить этот язык. Потому что, сделав
это, вы сможете понимать инструменты,
построенные на его основе, понимать,
зачем они были созданы и какие задачи
они решают. Изучение всех «почему?»
также поможет вам избежать неудачных
решений при проектировании кода.

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

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

7. Вы не делите концепции на
меньшие кусочки

Учеба должна быть постепенной

Изучение JavaScript подобно изучению
математики в школе. Вы сможете перейти
к сравнению чисел, основам алгебры и
т. д. только когда научитесь складывать,
вычитать, делить и умножать. Если вы
обнаруживаете, что вам сложно что-то
понять, зачастую это означает, что вы
где-то что-то «перепрыгнули» вместо
того чтобы продвигаться маленькими
шажками. Но перейти к изучению алгебры
без знания основ математики просто
невозможно.

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

Мы с вами знаем, что JavaScript — очень объемный язык программирования с различными вспомогательными библиотеками, фреймворками, базами данных и дополнительным функционалом. Нагромождение новой информации может отпугнуть начинающего программиста. Но вспомним слова автора книги «‎Вы не знаете JavaScript» Кайла Симпсона: «‎На JS сначала пишут код, а уже потом выясняют, как он работает» — давайте разберемся, как же его учить.

В этой статье я предлагаю пройтись по основным ошибкам, которые допускают новички при изучении JS. Читайте и запоминайте, на что обратить внимание во время самообучения и на собеседовании.

Учитесь поступательно

Достаточно взглянуть на роадмап разработчика, чтобы убедиться, насколько обширна экосистема JS и как много на первый взгляд надо выучить. Увидев такой массив информации, многие начинающие разработчики сами загоняют себя в ловушку и ошибочно думают, что это все необходимо знать сразу. Они пытаются разобраться со всей около JS экосистемой, не углубляясь в сам JS. Перескакивая между темами, они в итоге знают многое поверхностно. Такой подход не сделает их экспертами ни в одной из областей. Несмотря на то, что JavaScript быстро изменяющийся и растущий язык программирования, он имеет ряд фундаментальных принципов и базовых тем. Они используются и будут использоваться в будущем на проектах любой сложности.

Мое мнение: роадмап начинающего JS-разработчика должен выглядеть примерно так:

По такому плану мы учим основы основ:

  • как работает интернет — думаю, тут не надо объяснять, зачем это знать :)

  • верстку — да, этим тоже приходится регулярно заниматься JS-разработчику;

  • базовый JS — надо не просто знать, как объявить переменную, а все тонкости языка;

  • Git — незнание Git’а может отнимать драгоценное время на решение его проблем во время стажировки на проекте. Проверенный факт :)

  • фреймворк JS — я бы для начала взял React, но советую посмотреть все доступные фреймворки и выбрать себе для старта наиболее привлекательный. А для всех остальных задачи всегда найдутся.

За каждым шагом здесь скрывается обширный массив данных. Разберитесь, чем отличается клиент от сервера, углубитесь в HTML и CSS и определитесь, какой JS-фреймворк (Angular, React или Vue) больше всего соответствует вашим ожиданиям от профессии, и приступайте к обучению вдумчиво. С этими знаниями у вас будет больше шансов успешно пройти первое собеседование. А глубокое понимание основ JavaScript останется с вами на всю жизнь. Отсюда вытекает первое правило: подходите к изучению JavaScript систематически, не распыляйте свое внимание в надежде изучить все и сразу.

Глубоко вникайте в базовые темы

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

Зачастую половина начинающих разработчиков, отвечая на вопрос, что будет с переменными a, b, c, допускают ошибки. Многие считают, что тут должны возвращаться булевые значения — true или false. Но при таком коде, если все значения истинные, вернется последнее истинное значение или первое ложное, если хоть бы одно из них ложное. В нашем случае — 2, 18, 0 соответственно. Вот еще пример самой попсовой задачи из мира JS:

Полностью разобравшись лишь с одним этим примером, вы разберетесь и в таких концепциях, как

  • области видимости;

  • замыкания;

  • разница в работе переменных;

  • всплытие переменных;

  • как работает setTimeout и асинхронный JS.

Из этого следует второе правило: разбирая такие примеры, глубже вникайте в решения, которые предлагаются.

Не мыслите «‎стандартами»

Часто на разных ресурсах встречается разделение на «‎старый стандарт JS‎» и «‎‎новый стандарт JS». Якобы фичи из ES6, ES7 и последующих версий JavaScript — это дополнительные инструменты, которые можно выучить, устроившись в команду. У начинающих программистов складывается ошибочное впечатление, что можно учить их по отдельности, но это не так. Все фичи — деструктуризация, стрелочные функции, Spread-операторы, промисы, классы — уже давно используются, как современный стандарт языка. Знать, как с этим работать, крайне необходимо.

Теория без практики — JS на ветер

Допустим, вы прошли свое первое собеседование на вакансию Junior JavaScript Developer и показали потрясающее знание теории. Вас берут на стажировку в команду и доверяют первый таск. И тут вы понимаете, что не в состоянии самостоятельно написать ни строчки кода! Самое время упомянуть о третьем важном правиле: всегда укрепляйте свои теоретические знания на практике. Благодаря систематической практике вы научитесь не только быстро решать прикладные задачи, но и сможете ориентироваться в основных концепциях работы JavaScript. Для потенциальных заказчиков — это ценный скил.

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

Уверен, многие новички легко распишут решение через создание цикла при помощи for и переменной result с присвоенным ей нулевым значением. Выглядит не очень изящно, но зато работает, верно?

Но как можно переиспользовать этот метод для другой последовательности выбранных товаров — видимо оборачивать это все в функцию, но зачем нам это делать, если JS давно уже предоставляет инструменты для таких задач, вот пример с reduce:

Вот еще пример:

Естественно, этот код можно и дальше улучшать. Однако даже его достаточно, чтобы попрактиковать работу с запросами, массивами, объектами. В интернете есть множество бесплатных API, которые позволяют отрабатывать навыки работы с подобным функционалом. Например, такие ресурсы:

API «Звездных войн»: SWAPI ;alexwohlbruck/cat-facts ;

jikan.docs.apiary.io;

вездесущий гугл :)

Почему код должен быть лаконичным и понятным

Пренебрежительное отношение к неймингу переменных в коде — еще одна ошибка джунов. Читабельность кода является основным критерием того, что вас поймут другие программисты из вашей команды, в том числе вы сами, вернувшись к коду через пару месяцев. Для примера разберем массив пользователей, каждому из которых присвоено значение имени и возраста:

Предположим, при работе с данным массивом вы использовали метод обработки Map, что логично, но почему-то вы назвали параметр анонимной функции вы назвали itm:

Почему здесь это следует воспринимать как ошибку, ведь с этим или другим названием переменной все будет работать? Ошибка здесь, конечно же, не программная, а логическая. Если вы работаете с массивом данных users, почему бы не назвать каждый элемент этого массива user? Таким образом вы не будете получать гневные фидбэки от других инженеров о том, что они попросту не понимают, за что отвечает и или иная переменная в коде.

Вот еще один пример ненужного нагромождения символов в коде:

С помощью простой деструктуризации объектов массива (name, surname, age) можно получить лаконичный и понятный каждому программисту текст:

Так мы подошли к четвертому правилу: вырабатывайте хорошие привычки во время обучения, и вам не надо будет привыкать к этому во время работы.

Учитесь учиться

В программировании важно никогда не останавливаться на достигнутом. IT-сфера стремительно развивается: появляются новые фреймворки и метрики работы с данными. Если постепенно не осваивать их, вы рискуете надолго зависнуть в рутинных и однообразных задачах. А ведь вы не за этим пришли в профессию, верно? :) Обладая базовыми навыками программирования на JS, похвалите себя, переведите немного дух и возвращайтесь к изучению полноценного роадмапа по JavaScript.

На первый взгляд, синтаксис JS прост и интуитивно понятен, что подкупает. Однако это не мешает тем, кто делает первые (или не первые) шаги в нём, допускать ошибки. Мы попытались осветить некоторые промахи начинающих разработчиков.

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

Мария Кожанова

Программист и преподаватель

Переменная не определена либо недоступна в текущей области кода

Такое происходит сплошь и рядом — обращение к переменной, которая ранее не была объявлена либо недоступна в текущей области кода. Например, код:

<script>
let y = x + 1
</script>

Без предварительного определения переменной x породит ошибку.

Uncaught ReferenceError: x is not defined

Кроме того, переменная должна быть видна в текущем контексте. Сравните два примера.

Код 1.

<script>
function summa () {
  let a = 2
  let b = 3;
  return a + b;
}
console.log(a); //Uncaught ReferenceError
</script>

Код 2.

<script>
let a = 2
let b = 3;
function summa () {
  return a + b;
}
console.log(summa());
</script>

Код 1 на шестой строке выдаёт ошибку.

Uncaught ReferenceError: Cannot access 'a' before initialization

Дело в том, что переменная a определена внутри функции и недоступна за её пределами.
А код 2 без ошибок вернёт 5, потому что функция summa() имеет доступ ко всем переменным, определённым в глобальной области. Понимание области видимости важно в Javascript, области могут быть вложенными друг в друга, при этом дочерние области видимости будут иметь доступ к  родительским, но не наоборот.

Неверно организовано условие сравнения if

Чтобы записать условие сравнения переменной с каким-либо значением, используется оператор ==.

<script>
if(x == some_value) {}
</script>

Однако если ошибиться и использовать одинарный знак =, то вместо сравнения в круглых скобках окажется присваивание.

<script>
if (x = some_value) {}
</script>

В JavaScript эта операция будет истинной, в то время как иные языки программирования могут среагировать ошибкой.

Подвид предыдущей ошибки — когда переменная сравнивается не просто со значением, а с конкретным — единицей.

<script>
var x = some_value;
if (x = 1) { … }
</script>

Тут дьявол в деталях. По-булевски единица — это истина, а ноль — ложь. В результате выражение будет истинно.

Сравнение значений разных типов

Иной раз сравниваются переменные, которым были присвоены значения разных типов.

<script>
x = prompt('Введите число:', 0);
y = prompt('Введите другое число:', 0);
if (y < x){
console.log("Первое число " + x + " больше " + y);
}
else if (y > x){
 	console.log("Второе число " + y + " больше " + x);
}
else if (x == y){
 	console.log(x + " равно " + y);
};
</script>

Ввожу 5 и 125, а мне в итоге пишут, что 5 больше 125, почему?

Отвечаем: в этом примере переменные x и y сравниваются как строки, а не как числа, так как после получения значений они так и остались строками.

Ситуация изменится, если приведём значения переменной к числу.

…
x = parseInt(x)
y = parseInt(y)
…

К слову, в коде полезен === . Это оператор строгого равенства, который проверяет сравнимое с учётом типов.

if(x === y) { … }
</script>

В этом фрагменте кода сравнение сразу вернёт ложь, если переменные разных типов.

Объявленные в цикле переменные не исчезают после его завершения

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

<script>
for (let i = 0; i <= 100; i++) {
// некий код
}
console.log (i) //приведёт к выбросу исключения ReferenceError
</script>

Если попытаться использовать переменную i после выхода из цикла, то код в строке 4 приведёт к ошибке.

Переменная ликвидируется после окончания цикла, её значение будет не определено. В этом можно убедиться:

console.log(typeof i == typeof undefined)  // выведет true

Попробуем при объявлении счётчика в цикле вместо let (это объявление переменной с блочной областью видимости) использовать директиву var.

<script>
for (var i = 0; i <= 100; i++) {
// некий код
}
console.log (i); 
</script>

Тогда i после выхода из цикла продолжит жить своей жизнью.

Некорректное использование this

Ошибка возникает, когда специальное ключевое слово this отсутствует в коде либо используется неправильно. Ключевое слово this ссылается на контекст выполняемой функции. Оно помогает работать с объектами. Но имеет значение контекст — какой именно объект в данный момент кода будет описан словом this.  

<script>
function myFunction() {
  var myObject = {
    objProperty: "текстовое свойство",
    objMethod: function() {
      console.log(objProperty); //код, который приведёт к ошибке 
    }
  };
  myObject.objMethod();
}
myFunction();
</script>

В строке 5 переменная objProperty не определена, код вернёт ошибку.

Uncaught ReferenceError: objProperty is not defined

А всё потому, что пропущено ключевое слово this. Если внутри объекта myObject требуется обратиться к значению его свойства, то это делается следующим образом: this.objProperty.

Другой пример:

<script>
Form.prototype.submit = function () {
  this.clearLocalStorage();
  this.timer = setTimeout(function() {
    this.clearView();  //код, который приведёт к ошибке
  }, 100);
};
</script>

Ключевое слово this на строке 4 употребляется, однако код вернёт ошибку.

Uncaught TypeError: this.clearView is not a function

В этой строке контекст для this  — уже область функции setTimeout (объект window). 

Чтобы достичь нужного контекста, следует обратиться к нему, сделать это можно разными путями:

  1. Совместимым со старыми браузерами способом — определить выше переменную и использовать в нужный момент именно её.

…
var self = this;  
  this.timer = setTimeout(function() {
    self.clearView();
  }, 100);
…
  1. Использовать метод bind(), который работает не в совсем старых версиях браузеров.

Form.prototype.submit = function () {
  this.clearLocalStorage();
  this.timer = setTimeout(this.countTime.bind(this), 100);
};
Form.prototype.countTime = function(){
  this.clearView();
};

Использование alert

Это, скорее, не ошибка, а рекомендация более дружелюбного общения с посетителем. Чтобы мгновенно увидеть значение переменной value, можно просто взять и написать alert(value). Однако это не всегда поможет для решения сиюминутной проблемы. Например, если переменная value является объектом, то это мало что даст. Но существует набор решений для разных случаев.

Как правило, вывод данных нужен в ситуациях, когда хочется:

  1. Что-то сказать посетителю, вывести информационное или предупредительное сообщение. Для этой цели применяют разные подходы с помощью jquery и bootstrap. К тому же в некоторых браузерах вывод данных с помощью alert блокирует экран и не даёт ничего сделать на странице, пока этот алерт не отожмёшь.

  2. Произвести отладку в ходе разработки. В этом случае можно использовать объект браузера Console. Например, console.log(value) выведет сообщение в веб-консоль.

Это лучше alert. Во-первых, как следует из названия, служебные данные выводятся в консоли. Туда заходит немного рядовых посетителей, и если вы вдруг забыли убрать отладочный вывод, его увидят далеко не все. Во-вторых, с помощью отладки в консоли можно детально смотреть на объекты, о чём вскользь упоминалось выше. 

Зададим объект и посмотрим на него разными способами.

<script>
  var Person = {
    name: 'Peter', age: 34, skills: ['HTML', 'Javascript', 'CSS']
  }
  alert(Person)
  console.log(Person)
</script>

Результат кода alert(Person).

И результат вывода в консоль. Как видите, более информативно.

Неверное подключение библиотек 

Такое происходит, когда библиотеку:

  • не подключили, 

  • не подключили вовремя, а библиотека или скрипт, которые в ней нуждаются, уже подключены/запущены.

И вместо галереи или слайдера поджидает ошибка.

Uncaught TypeError: Cannot read properties of undefined

Смотрим код.

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/assets/owl.carousel.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/assets/owl.theme.default.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/owl.carousel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
 
  <div class="owl-carousel">
    <div class="item">
      <h2>Item 1</h2>
    </div>
    <div class="item">
      <h2>Item 2</h2>
    </div>
    <div class="item">
      <h2>Item N</h2>
    </div>
  </div>
  <script>
    var owl = $('.owl-carousel');
    owl.owlCarousel({...})
  </script>

Библиотека Owl Carousel имеет в зависимостях Jquery, но подключается раньше.

В примере выше ошибку генерирует скрипт инициализации карусели.

Uncaught TypeError: owl.owlCarousel is not a function

У библиотеки не было шанса объявиться. Как правило, когда на странице используется ряд скриптов, ошибки плодятся как снежный ком.

Следует поменять местами подключения Jquery и Owl Carousel — строки 3 и 4.

Выводы

Избежать распространённых ошибок помогут практика и понимание того, как работает язык. Отследить баги помогут такие инструменты, как JSHint, JSLint, ESLint.

Кроме того, в IDE обычно имеются плагины для дебаггинга. В начале статьи мы говорили о том, что начинать изучение JavaScript можно с помощью редакторов типа Notepad. Но в удобной настроенной среде это делать гораздо приятнее и эффективнее.

Как вы слышали не раз, не ошибается тот, кто ничего не делает. Красное оповещение об ошибке в консоли не должно пугать. Это подсказка, что пошло не так, инструкция к действию — погуглить фразу Javascript <текст ошибки>.

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

Данный перечень ошибок в JavaScript будет полезен не только для js-кодеров, но и для людей, которые переходят в JavaScript из других языков.

Многие вещи, которые казались привычными в Ruby, Python, Java, C или даже PHP, в JS могут отсутствовать или работать иначе.

1. Точка с запятой нужна не всегда

Многие js-программисты слишком много внимания уделяют точкам с запятой в конце. На самом деле использовать их постоянно совсем не обязательно, а код без лишних символов выглядит чище.

var Thing = require('thing'),
    thing = new Thing(),
    stuff = thing.whatsit();

stuff.doStuff(function(err){
  if (err) console.log(err);
  else console.log('done');
})

vs

var Thing = require('thing')
var thing = new Thing()
var stuff = thing.whatsit()

stuff.doStuff(function(err){
  if (err) console.log(err)
  else console.log('done')
})

Использование большего количества var при объявлении переменных не замедляет работу программы, зато позволяет не использовать лишние символы в конце строки.

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

Рассмотрим следующий код:

function doStuff(thing) {
  var whatsit = new Whatsit(thing);
      stuff = new Stuff(whatsit);

  // что-то происходит

  return stuff
}

Переменная stuff находится в глобальной области видимости и не будет относиться к области видимости функции. Из-за повсеместного использования точек с запятой вам необходимо помнить, что нужно заменить точку с запятой на запятую, а точку с запятой добавить в конец строки. Если этого не сделать, ошибки не будет, но stuff, объявленная без var, при компиляции перенесется в самый верх и будет объявлена как глобальная переменная, что может привести к неприятным последствиям.

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

function doStuff(thing) {
  var whatsit = new Whatsit(thing)
  var stuff   = new Stuff(whatsit)

  // что-то происходит

  return stuff
}

2. Не возвращайте результат до завершения асинхронного обратного вызова

Одна из наиболее распространенных ошибок в JavaScript при работе с асинхронным кодом. Такую ошибку легко допустить, а затрагивает она одинаково программистов клиентской и серверной части.

Рассмотрим типичный код контроллера на Express или Sails.JS:

var myFunction = function(req, res) {
  User.findOne({name: req.body.name}).exec(function(err, user){
    if (err) return res.json(400, err)
    user.update({description: req.body.description}).exec(function(err, user){
      if (err) return res.json(400, err)
    })
    return res.json(user)
  })
}

Размещение return res.json(user) за пределами колбека функции update приведет к тому, что view, которое будет передано AJAX-запросом, увидит только пользователь, не обновлявший страницу.

Перемещение return внутрь колбэка update() поможет устранить проблему:

var myFunction = function(req, res) {
  User.findOne({name: req.body.name}).exec(function(err, user){
    if (err) return res.json(400, err)
    user.update({description: req.body.description}).exec(function(err, user){
      if (err) return res.json(400, err)
      return res.json(user)
    })
  })
}

Похожая ошибка, которая часто встречается на клиентской стороне:

var thing = null
var thingId = $("#thing-id").val()
$.get("/thing/" + thingId, function(data){
  thing = data.thing
  console.log("got thing", thing)
}).fail(function(err){
  console.log("got error", err)
})
$(".thing-name").text(thing)

Здесь проблема в том, что $(«.thing-name»).text(thing) будет выполнена перед тем, как data будет получена с сервера. Таким образом, thing останется null.

Правильный вариант:

var thingId = $("#thing-id").val()
$.get("/thing/" + thingId, function(data){
  console.log("got thing", data.thing)
  $(".thing-name").text(data.thing)
}).fail(function(err){
  console.log("got error", err)
})

Другая ошибка, которую часто допускают JavaScript-разработчики при работе с асинхронными запросами случается, когда нужно объединить два ответа из разных мест. Для примера, нам надо сделать два запроса к разным API и объединить ответы. Рассмотрим код:

function thingyWhatsit(thingId, whatsitId) {
  var thingData = null
  var whatsitData = null
  $.get("/thing/" + thingId, function(data){
    thingData = data
  }).fail(function(err){
    console.log("got thing error", err)
  })
  $.get("/whatsit/" + whatsitId, function(data){
    whatsitData = data
  }).fail(function(err){
    console.log("got whatsit error", err)
  })
  return $.extend(true, {}, thingData, whatsitData)
}

var result = thingyWhatsit(tid, wid)

И thingData, и whatsitData будут возвращаться к вызову return, а функция всегда будет возвращать {}.

Есть соблазн сделать так:

function thingyWhatsit(thingId, whatsitId) {
  $.get("/thing/" + thingId, function(thingData){
    $.get("/whatsit/" + thingId, function(whatsitData){
      return $.extend(true, {}, thingData, whatsitData)
    }).fail(function(err){
      console.log("got inner error", err)
    })
  }).fail(function(err){
    console.log("got error", err)
  })
}

var result = thingyWhatsit(tid, wid)

Это сработает. Но второй вызов будет ожидать выполнения первого, что совершенно неэффективно.

Лучше использовать стороннюю библиотеку, например async. async позволяет оформить вызовы таким образом:

function thingyWhatsit(thingId, whatsitId, callback) {
  async.parallel({
    thing: function(next) {
      $.get("/thing/" + thingId, function(data){
        next(null, data)
      }).fail(function(err){
        next(err, null)
      })
    },
    whatsit: (function(id) {
      $.get("/whatsit/" + whatsitId, function(data){
        next(null, data)
      }).fail(function(err){
        next(err, null)
      })
    }
  }, function(result) {
    if (result.err) {
      callback({errors: result.err}, null)
    } else {
      callback(null, $.extend(true, {}, result.thing, result.whatsit)
    }
  })
}

var result = thingyWhatsit(tid, wid)

Этот код позволяет выполнить запросы синхронно, и в случае ошибок вернуть их описание, а в случае корректного выполнения – получить совмещенный ответ.

3. Не забывайте, что = это не ==

Казалось бы, базовые операторы не должны вызывать проблем при использовании, однако невнимательность иногда приводит к глупым ошибкам.

var x = someFunction()

if (x = 'hello world') console.log("winning!", x)
else console.log("Dammit Chloe!", x)

Не имеет значения, что вернет someFunction() – вы всегда будете получать true, ведь в условии описана операция присваивания.

var x = someFunction()

if (x == 'hello world') console.log("winning!", x)
else console.log("ammit Chloe!," x)

Бывают и обратные ситуации:

var i == 10

В этом случае компилятор выдаст синтаксическую ошибку. Но что, если вы сделаете так:

function check(item, callback) {
  var item = new Item(),
      thing = new Thing(item),
      whatsit = New Wahtsit(thing),
      check_item = null;

for (var i = 0, l = items.length; i++) {
  check_item == items[i];
  setTimeout(function() {
    do_some_complex_stuff_with_checked_item();
    callback(check_item);
  }, 0);
}

Здесь строка check_item == items[i] будет просто расценена как false, значение никогда не будет присвоено check_item.

4. Не забывайте, что == это не ===, и что != не равно !==

Так же, как и в пункте 3, данные ошибки встречаются слишком часто. Помните, что оператор == сравнивает значения, а === дополнительно сравнивает типы.

5. Не используйте & там, где должно быть &&

Оператор & нужен для сравнения целых чисел, тогда как && сравнивает истинность операндов. Таким образом, 123 & false === 0 (потому что false становится 0), но 123 && false === false 9 & 5 === 1, а 9 && 5 === 5.

6. Есть разница между | и ||

Аналогично случаются ошибки, связанные с использованием операторов | и ||. Применение этих операторов аналогично описанному выше &, за исключением видов сравнения (здесь or вместо and).

7. Помните, что |= в JavaScript не то же, что ||= в Ruby

Эта ошибка встречается у Ruby-программистов.

x = some_thing_maybe_nil
# ...
x ||= 5

В Ruby, если бы x был nil, он бы равнялся 5. А если, к примеру, 9, то он так и остался бы с этим значением.

var x = some_thing_maybe_null()
// ...
x |= 5

В JavaScript данный код либо вернет ошибку, либо вернет 13, если x === 9.

8. Не путайте nil из Ruby c null в JavaScript (и наоборот)

Это одна из самых актуальных ошибок в JavaScript для Ruby-разработчиков. Ruby-программисты часто считают, что nil в Ruby будет означать то же, что и null в JavaScript. Однако понятия nil в JavaScript нет и невнимательность может привести к ошибке:

var x = nil

Здесь x будет содержать undefined, а не null.

9. Помните, что this не всегда то, чем кажется

function Thing(name) {
  this.name = name
}
Thing.prototype.delayedName = function(callback){
  setTimeout(function() {
    callback(this.name)
  }, 1000)
}
var thing = new Thing('whatsit')
thing.delayedName(function(name){
  console.log("clicked", name.toUpperCase())
})

Код выше только вызовет ошибку Uncaught TypeError: undefined is not a function.

Чтобы избежать этого, можно сделать так:

function Thing(name) {
  this.name = name
  var that = this
  this.delayedName = function(callback){
    setTimeout(function() {
      callback(that.name.toUpperCase())
    }, 1000)
  }
}
var thing = new Thing('whatsit')
thing.delayedName(function(name){
  console.log("clicked", name.toUpperCase())
})

Такой способ сработает, но приведет к утечке памяти: ссылка that не будет объявлена корректно.

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

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

Таким образом, этот код будет работать:

var i = 0
for (var ii = 0; ii < 9; ii++){
  i += 1
}
console.log("i = ", i)

Этот код тоже будет работать и не приведет к утечке памяти:

function delay(seconds, message) {
  setTimeout((function(startTime){
    var duration = (new Date() - startTime) / 1000.0
    console.log("delay of ", seconds, "for message", message, "actually took", duration, "seconds" )
  })(new Date()), seconds)
}

Однако так работать не будет:

function add(firstNumber, secondNumber) {
  var i = firstNumber + secondNumber
}

add(7, 666)
console.log("i =", i)

В этом случае i будет undefined.

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

Надеемся, после нашей статьи вы больше не совершите ни одной из перечисленных ошибок в JavaScript.

Больше советов по JavaScript:

  • Разбираем JavaScript код: 7 проблем, ухудшающих читабельность
  • Какой JavaScript фреймворк следует учить в 2018 году
  • Скрытые фичи JavaScript

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

Новички часто допускают несколько распространенных ошибок, которые аукаются им и всплывают в тот момент, когда они меньше всего этого ожидают. Чтобы понять, какие это ошибки, прочитайте эту статью.

  • Пропуск фигурных скобок
    • JS
  • Отсутствие точек с запятой
    • JS
  • Непонимание приведений типа
    • JS
    • HTML
  • Забытые var
    • JS
  • Арифметические операции с плавающей точкой
    • JS
  • Использование конструкторов вместо оригинальных обозначений
    • JS
  • Непонимание того, как разделяются диапазоны
    • JS
  • Использование Eval
    • JS
  • Непонимание асинхронного кода
    • JS
  • Злоупотребление отслеживанием событий
    • JS
    • HTML
  • Заключение

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

Смотрите пример, приведенный ниже:

// Этот код не делает то, что должен!
    if(name === undefined)
        console.log('Please enter a username!');
        fail();
    // До этой строки исполнение никогда не дойдет:
    success(name);
}
function success(name){
    console.log('Hello, ' + name + '!');
}
function fail(){
    throw new Error("Name is missing. Can't say hello!");
}

Хотя вызов fail() имеет отступ и, кажется, будто он принадлежит оператору if , это не так. Он вызывается всегда. Так что это полезная практика окружать все блоки кода фигурными скобками, даже если в них присутствует только один оператор.

Во время парсировки JavaScript осуществляется процесс, известный как автоматическая расстановка точек с запятой. Как следует из названия, анализатор расставляет недостающие знаки вместо вас.

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

Вот пример:

// Результатом обработки этого кода станет вывод сообщения об ошибке. Добавление точки с запятой решило бы проблему.
console.log('Welcome the fellowship!')
['Frodo', 'Gandalf', 'Legolas', 'Gimli'].forEach(function(name){
    hello(name)
})
function hello(name){
    console.log('Hello, ' + name + '!')
}

Так как в строке 3 отсутствует точка с запятой, анализатор предполагает, что открывающаяся скобка в строке 5 является попыткой доступа к свойству, используя синтаксис массива аксессора (смотри ошибку № 8), а не отдельным массивом, который является не тем, что предполагалось.

Это приводит к ошибке. Исправить это просто — всегда вставляйте точку с запятой.

Некоторые опытные разработчики JavaScript предпочитают не расставлять точки с запятой, но они прекрасно знают о возможных ошибках, и то, как их предотвратить.

JavaScript поддерживает динамические типы. Это означает, что вам не нужно указывать тип при объявлении новой переменной, вы можете свободно переназначить или конвертировать его значение.

Это делает JavaScript гораздо более простым, чем, скажем, C# или Java. Но это таит в себе потенциальную опасность ошибок, которые в других языках выявляются на этапе компиляции.

Вот пример:

// Ожидание события ввода из текстового поля
var textBox = document.querySelector('input');
textBox.addEventListener('input', function(){
    // textBox.value содержит строку. Добавление 10 содержит 
    // строку '10', но не выполняет ее добавления..
    console.log(textBox.value + ' + 10 = ' + (textBox.value + 10));
});
<input type="number" placeholder="Введите число" />

Проблема может быть легко исправлена с применением parseInt(textBox.value, 10), чтобы перевести строку в число перед добавлением к ней 10.

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

Чтобы не допустить преобразования типа при сравнении переменных в операторе if, вы можете использовать проверку строгого равенства (===).

Еще одна ошибка, допускаемая новичками — они забывают использовать ключевое слово var при объявлении переменных. JavaScript — очень либеральный движок.

В первый раз, когда он увидит, что вы использовали переменную без оператора var, он автоматически объявит его глобально. Это может привести к некоторым ошибкам.

Вот пример, который кроме того иллюстрирует и другую ошибку — недостающую запятую при объявлении сразу нескольких переменных:

var a = 1, b = 2, c = 3;
function alphabet(str){
    var a = 'A', b = 'B'    // Упс, здесь пропущена ','!
        c = 'C', d = 'D';
    return str + ' ' + a + b + c + '…';
}
console.log( alphabet("Let's say the alphabet!") );
// О, нет! Что-то не так! У c новое значение!
console.log(a, b, c);

Когда анализатор достигает строки 4, он автоматически добавит точку с запятой, а затем интерпретирует объявления c и d в строке 5, как глобальные.

Это приведет к изменению значения другой переменной c. Больше о подводных камнях JavaScript здесь.

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

Например:

var a = 0.1, b = 0.2;
// Сюрприз! Это неправильно:
console.log(a + b == 0.3);
// Потому что 0.1 + 0.2 не дает в сумме то число, что вы ожидали:
console.log('0.1 + 0.2 = ', a + b);

Чтобы обойти эту проблему, вы не должны использовать десятичные числа, если вам нужна абсолютная точность — используйте целые числа, или если вам нужно работать с денежными единицами, используйте библиотеку типа bignumber.js.

Когда программисты Java и C # начинают писать на JavaScript, они часто предпочитают создавать объекты с использованием конструкторов: new Array(), new Object(), new String().

Хотя они прекрасно поддерживаются, рекомендуется использовать оригинальные обозначения: [], {}, «», так как конструкторы имеют свои особенности:

var elem4 = new Array(1,2,3,4);
console.log('Four element array: ' + elem4.length);
// Создание массива из одного элемента. Это не работает так, как вы думаете:
var elem1 = new Array(23);
console.log('One element array? ' + elem1.length);
/* Объекты строки также имеют свои особенности */
var str1 = new String('JavaScript'),
    str2 = "JavaScript";
// Строгое равенство не соблюдается:
console.log("Is str1 the same as str2?", str1 === str2);

Решение этой проблемы просто: попробуйте всегда использовать буквальные оригинальные
обозначения. Кроме того, в JS не обязательно указывать размер массивов заранее.

Одна из трудных для понимания новичками вещей в JS, это правила разграничения и закрытия диапазонов. И это действительно не просто:

for(var i = 0; i < 10; i++){
    setTimeout(function(){
        console.log(i+1);
    }, 100*i);
}
/* Чтобы исправить проблему, заключите код в выражение самовыполняющейся функции:
for(var i = 0; i < 10; i++){
    (function(i){
        setTimeout(function(){
            console.log(i+1);
        }, 100*i);
    })(i);
}               
*/

Функции сохраняют связь с переменными в пределах родительских диапазонов. Но поскольку мы откладываем выполнение через setTimeout, когда наступит время для запуска функций, то цикл будет уже фактически завершен и переменная i увеличивается до 11.

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

Eval есть зло. Это считается плохой практикой, и в большинстве случаев, когда вы думаете его применить, можно найти лучший и более быстрый способ.

// Это плохая практика. Пожалуйста, не делайте так:
console.log( eval('obj.name + " is a " + obj.' + access) );
// Вместо этого для доступа к свойствам динамически используйте массив примечаний:
console.log( obj.name + " is a " + obj[access]);
/* Использование eval в setTimout */
// Это также неудачная практика. Она медленна и сложна для проверки и отладки:
setTimeout(' if(obj.age == 30) console.log("This is eval-ed code, " + obj[access] + "!");', 100);
// Так будет лучше:
setTimeout(function(){
    if(obj.age == 30){
        console.log('This code is not eval-ed, ' + obj[access] + '!');
    }
}, 100);

Код внутри eval — это строка. Отладочные сообщения, связанные с Eval-блоками непонятны, и вам придется поломать голову, чтобы правильно расставить одинарные и двойные кавычки.

Не говоря уже о том, что это будет работать медленнее, чем обычный JavaScript. Не используйте Eval если вы не знаете точно, что вы делаете.

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

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

Вот пример, в котором я использую сервис FreeGeoIP для определения вашего местоположения по IP-адресу:

// Определение данных местоположения текущего пользователя.
load();
// Вывод местоположения пользователя. Упс, это не работает! Почему?
console.log('Hello! Your IP address is ' + userData.ip + ' and your country is ' + userData.country_name);
// Загружаемая функция будет определять  ip текущего пользователя и его местоположение
// через ajax, используя сервис freegeoip. Когда это сделано она поместит возвращаемые
// данные в переменную userData.
function load(){
    $.getJSON('http://freegeoip.net/json/?callback=?', function(response){
        userData = response;
        // Выведите из комментариев следующую строку, чтобы увидеть возвращаемый
        // результат:
        // console.log(response);
    });
}

Несмотря на то, что console.log располагается после вызова функции load(), на самом деле он выполняется перед определением данных.

Давайте предположим, что вы хотите отслеживать клик кнопки, но только при условии установленного чеккера.

Новичок в этом случае может сделать следующее (используя JQuery):

button = $('button');
// Мы хотим отслеживать клик при установленном чеккере в чекбоксе.
checkbox.on('change', function(){
    // Чеккер установлен?
    if(this.checked){
        // Отслеживание клика кнопки. 
        button.on('click', function(){
            // Это предупреждение появляется несколько раз. Почему?
            alert('Hello!');
        });
    }
});
<input type="checkbox" />

<button>Кликните меня!</button>

<p>Кликните по чекбоксу несколько раз.</p>

Что-то, очевидно, не так. В идеале, вы должны отслеживать события только один раз, как задано для изменения события чекбокса.

Повторяющийся вызов button.on(‘click’ ..) приводит к повторяющемуся отслеживанию события, которое никогда не удаляется.

Я оставлю данный пример без изменений в качестве упражнения для читателей, чтобы они могли поработать над ним 🙂

Лучшим способом избежать подобных ошибок является использование JSHint. Некоторые IDE предлагают встроенную интеграцию с инструментом, который проверяет код во время написания.

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

Понравилась статья? Поделить с друзьями:

Не пропустите эти материалы по теме:

  • Яндекс еда ошибка привязки карты
  • Ошибки при измерении штангенциркулем
  • Ошибки при клейки обоев
  • Ошибки при кладке плитки
  • Ошибки при измерении соэ

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии