10 ошибок, которые часто допускают новички JavaScript

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

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

1. Пропуск фигурных скобок

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

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

JS

// Этот код не делает то, что должен!
    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 , это не так. Он вызывается всегда. Так что это полезная практика окружать все блоки кода фигурными скобками, даже если в них присутствует только один оператор.

2. Отсутствие точек с запятой

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

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

Вот пример:

JS

// Результатом обработки этого кода станет вывод сообщения об ошибке. Добавление точки с запятой решило бы проблему.
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 предпочитают не расставлять точки с запятой, но они прекрасно знают о возможных ошибках, и то, как их предотвратить.

3. Непонимание приведений типа

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

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

Вот пример:

JS

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

HTML

<input type="number" placeholder="Введите число" />

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

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

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

4. Забытые var

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

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

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

JS

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 здесь.

5. Арифметические операции с плавающей точкой

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

Например:

JS

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.

6. Использование конструкторов вместо оригинальных обозначений

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

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

JS

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 не обязательно указывать размер массивов заранее.

7. Непонимание того, как разделяются диапазоны

Одна из трудных для понимания новичками вещей в 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 и хранит его копию для каждой задержки выполнения функции. Больше о диапазонах вы можете узнать здесь и здесь.

8. Использование Eval

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

JS

// Это плохая практика. Пожалуйста, не делайте так:
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 если вы не знаете точно, что вы делаете.

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

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

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

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

JS

// Определение данных местоположения текущего пользователя.
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(), на самом деле он выполняется перед определением данных.

10. Злоупотребление отслеживанием событий

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

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

JS

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

HTML

<input type="checkbox" />

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

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

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

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

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

Заключение

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

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

РедакцияПеревод статьи «10 Mistakes That JavaScript Beginners Often Make»