img

Девять самых распространенных ошибок, которые разработчики допускают при работе с JavaScript

21 ноября
20:00
Бесплатный вебинар
Введение в Docker
Ведущий — Филипп Игнатенко.
Руководитель центра разработки
Записаться
img
img

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

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

Путаница с операторами присваивания (=) и равенства (==, ===)

Само название говорит о том, что оператор присваивания (=) используется для того, чтобы присваивать значения переменным. Разработчики часто путают его с оператором равенства.

Вот пример:

const name = "javascript";
if ((name = "nodejs")) {
    console.log(name);
}
// output - nodejs

В данном случае переменная name и строка «nodejs» не сравниваются. Вместо этого строковое значение «nodejs» присваивается переменной name и выводится в консоль. 

В JavaScript операторами сравнения является двойной и тройной знак равенства (== и ===, соответственно). 

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

const name = "javascript";
if (name == "nodejs") {
    console.log(name);
}
// no output
// OR
if (name === "nodejs") {
    console.log(name);
}
// no output

Разница между этими двумя операторами сравнения в том, что двойной знак равенства выполняет нестрогое сравнение, а тройной – строгое

При нестрогом сравнении сравниваются только значения. А при строгом – значения и тип данных. 

Следующий пример поможет лучше это понять:

const number = "1";
console.log(number == 1);
// true
console.log(number === 1);
// false

Переменной number было присвоено строковое значение 1. При сравнении с 1 (числового типа) с помощью двойного знака равенства, оператор вернет значение true, так как оба значения равны 1. 

Но при сравнении с помощью тройного знака равенства оператор вернет false, так как эти значения имеют разный тип данных. 

Ожидание, что обратные вызовы будут синхронными

Обратные вызовы – это один из способов обработки асинхронных операций, который использует JavaScript. Наиболее предпочтительными методами обработки асинхронных операций все же являются promise и async/await, так как большое количество обратных вызовов приводит к самом настоящему аду. 

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

Отличный пример – функция setTimeout, которая получает в качестве первого аргумента функцию обратного вызова, а в качестве второго – время (мс):

function callback() {
??    console.log("I am the first");
??}
??setTimeout(callback, 300);
??console.log("I am the last");
??// output
??// I am the last
??// I am the first

По прошествии 300 миллисекунд вызывается функция обратного вызова. Но до того, как они закончатся, остальная часть кода выполняется. Именно поэтому последний console.log был выполнен в первую очередь. 

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

Вот так эта ошибка выглядит:

function addTwoNumbers() {
??    let firstNumber = 5;
??    let secondNumber;
??    setTimeout(function () {
??        secondNumber = 10;
??    }, 200);
??    console.log(firstNumber + secondNumber);
??}
??addTwoNumbers();
??// NaN

Здесь результат – это NaN, так как значение secondNumber не определено. Когда происходит выполнение операции firstNumber + secondNumber, значение secondNumber все еще не определено, поскольку функция setTimeout выполнит обратный вызов только через 200 мс. 

Лучший способ обойти это – выполнить остальную часть кода в функции обратного вызова:

function addTwoNumbers() {
??    let firstNumber = 5;
??    let secondNumber;
??    setTimeout(function () {
??        secondNumber = 10;
??        console.log(firstNumber + secondNumber);
??    }, 200);
??}
??addTwoNumbers();
??// 15

Неправильные ссылка на this

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

Ниже приведен пример распространенной ошибки при использовании this

const obj = {
??    name: "JavaScript",
??    printName: function () {
??        console.log(this.name);
??    },
??    printNameIn2Secs: function () {
??        setTimeout(function () {
??            console.log(this.name);
??        }, 2000);
??    },
??};
??obj.printName();
??// JavaScript
??obj.printNameIn2Secs();
??// undefined

Первый результат – это JavaScript, так как this.name правильно указывает на свойство имени объекта. Второй результат - undefined, так как this потерял ссылку на свойство объекта (включая имя). 

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

this в obj.printName() указывает непосредственно на obj. this в obj.printNameIn2Secs() указывает непосредственно на obj. Но this в функции обратного вызова setTimeout не указывает ни на один объект, потому что ни один объект ее не вызвал. 

Чтобы объект вызывал setTimeout, необходимо выполнить что-то на подобие obj.setTimeout… А так как объекта, который вызывает эту функцию, нет, то используется объект по умолчанию (то есть window). 

У window нет свойства name, поэтому результатом становится undefined.

Лучший способ сохранить ссылку на this в setTimeout - это воспользоваться функциями bind?call?apply или стрелочной функцией (введенные в ES6). В отличие от обычных функций, стрелочные функции не создают собственный this.

Таким образом, в следующем примере ссылка на this сохраняется:

const obj = {
??    name: "JavaScript",
??    printName: function () {
??        console.log(this.name);
??    },
??    printNameIn2Secs: function () {
??        setTimeout(() => {
??            console.log(this.name);
??        }, 2000);
??    },
??};
??obj.printName();
??// JavaScript
??obj.printNameIn2Secs();
??// JavaScript

Пренебрежение изменяемостью объектов

В JavaScript вместо простых типов данных, таких как строковый, числовой и т.д. объекты имеют ссылочный тип данных. Например, объекты типа ключ-значение:

const obj1 = {
??    name: "JavaScript",
??};
??const obj2 = obj1;
??obj2.name = "programming";
??console.log(obj1.name);
??// programming

obj1 и obj2 имеют одинаковую ссылку на место в памяти, где хранится объект. 

Или массивы:

const arr1 = [2, 3, 4];
??const arr2 = arr1;
??arr2[0] = "javascript";
??console.log(arr1);
??// ['javascript', 3, 4]

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

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

Лучший способ избежать этого – всегда создавать новые ссылки для новых объектов, если вы вдруг хотите продублировать их. Здесь идеально подойдет оператор rest (введенный в ES6).

Вот пример для объектов типа ключ-значение:

const obj1 = {
??    name: "JavaScript",
??};
??const obj2 = { ...obj1 };
??console.log(obj2);
??// {name: 'JavaScript' }
??obj2.name = "programming";
??console.log(obj.name);
??// 'JavaScript'

А вот для массивов:

const arr1 = [2, 3, 4];
??const arr2 = [...arr1];
??console.log(arr2);
??// [2,3,4]
??arr2[0] = "javascript";
??console.log(arr1);
??// [2, 3, 4]

Сохранение массивов и объектов в хранилище браузера

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

Если вы попытаетесь сохранить объекты, то JavaScript конвертирует их в строки. Для объектов результатом будет [Object Object], а для элементов массива - строка, разделенная запятыми. 

Например,

const obj = { name: "JavaScript" };
??window.localStorage.setItem("test-object", obj);
??console.log(window.localStorage.getItem("test-object"));
??// [Object Object]
??const arr = ["JavaScript", "programming", 45];
??window.localStorage.setItem("test-array", arr);
??console.log(window.localStorage.getItem("test-array"));
??// JavaScript, programming, 45

Когда объекты сохраняются таким образом, то к ним становится сложнее получить доступ. В случае приведенного выше примера, если вы попробуете получить доступ к объекту с помощью чего-то вроде .name, это приведет к ошибке. Это происходит, потому что[Object Object] теперь является строкой, и у нее нет свойства name.

Лучший способ, чтобы сохранить объекты и массивы в локальном хранилище – это использовать JSON.stringify (для конвертирования объектов в строки) и JSON.parse (для конвертирования строк в объекты). И таким образом, получить доступ к объектам станет намного проще. 

Ниже приведена корректная версия приведенного выше кода:

const obj = { name: "JavaScript" };
??window.localStorage.setItem("test-object", JSON.stringify(obj));
??const objInStorage = window.localStorage.getItem("test-object");
??console.log(JSON.parse(objInStorage));
??// {name: 'JavaScript'}
??const arr = ["JavaScript", "programming", 45];
??window.localStorage.setItem("test-array", JSON.stringify(arr));
??const arrInStorage = window.localStorage.getItem("test-array");
??console.log(JSON.parse(arrInStorage));
??// JavaScript, programming, 45

Не использование значений по умолчанию

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

function addTwoNumbers(a, b) {
??    console.log(a + b);
??}
??addTwoNumbers();
??// NaN

Здесь результатом является NaN, потому что a и b не определены (undefined). Если бы использовались значения по умолчанию, то такой ошибки можно было бы избежать. Например,

function addTwoNumbers(a, b) {
??    if (!a) a = 0;
??    if (!b) b = 0;
??    console.log(a + b);
??}
??addTwoNumbers();
??// 0

Или же можно использовать функцию задания значений по умолчанию, которая была введена в ES6:

function addTwoNumbers(a = 0, b = 0) {
??    console.log(a + b);
??}
??addTwoNumbers();
??// 0

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

Неправильное присвоение имен переменным

Да, разработчики до сих пор допускают такую ошибку. Присвоение имен - это непросто, но у разработчиков просто нет выбора. Комментарии – это такая же обычная практика в программировании, как и присвоение имен переменным. 

Например, 

function total(discount, p) {
??    return p * discount
??}

С переменной discount все хорошо, но что насчет p или total? Всего (total) чего? Лучше сделать вот так:

function totalPrice(discount, price) {
??    return discount * price
??}

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

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

Проверка логических значений

const isRaining = false
??if(isRaining) {
??    console.log('It is raining')
??} else {
??    console.log('It is not raining')
??}
??// It is not raining

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

В JavaScript нестрогое сравнение 0? и false возвращает true, и нестрогое сравнение 1 и true также возвращает true. Это значит, что если бы переменная isRaining была равна 1, то isRaining был бы равен true.

Такую ошибку также часто допускают при работе с объектами. Например, 

const obj = {
??    name: 'JavaScript',
??    number: 0
??}
??if(obj.number) {
??    console.log('number property exists')
??} else {
??    console.log('number property does not exist')
??}
??// number property does not exist

Несмотря на то, что свойство number существует, obj.number? возвращает 0, что является ложным значением (false), и поэтому выполняется блок else.

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

if(a === false)...
if(object.hasOwnProperty(property))...

Путаница со сложением и конкатенацией

Знак плюс (+) в JavaScript выполняет две функции: сложение и конкатенацию. Сложение – это для чисел, а конкатенация – для строк. Некоторые разработчики часто неправильно используют данный оператор.

Например,

const num1 = 30;
??const num2 = "20";
??const num3 = 30;
??const word1 = "Java"
??const word2 = "Script"
??console.log(num1 + num2);
??// 3020
??console.log(num1 + num3);
??// 60
??console.log(word1 + word2);
??// JavaScript

Если вы складываете строки и числа, то JavaScript конвертирует числа в строки и объединяет все значения. А для сложения чисел выполняется математическая операция сложения. 

Заключение

Ошибок, конечно, гораздо больше (некоторые более тривиальные, некоторые более серьезные), чем мы перечислили здесь. Так что, будьте в курсе того, как развивается язык. 

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

Ссылка
скопирована
Получите бесплатные уроки на наших курсах
Все курсы
Программирование
Скидка 25%
Фронтенд-разработчик с нуля
Погрузитесь в мир веб-разработки, освоив основные инструменты работы: HTML, CSS, JavaScript. Научитесь работать с дизайн-макетами и адаптивной версткой, сверстаете свои первые страницы и поймете, как строить карьерный трек в ИТ.
Получи бесплатный
вводный урок!
Пожалуйста, укажите корректный e-mail
отправили вводный урок на твой e-mail!
Получи все материалы в telegram и ускорь обучение!
img
Еще по теме:
img
Гипервизор - это программное обеспечение для виртуализации, используемое для создания и запуска виртуальных машин (ВМ). Гипервиз
img
Виртуализация серверов позволяет запускать несколько виртуальных машин на одном физическом сервере. Запуск виртуальных машин (ВМ
img
Сегодня мы рассмотрим, как настроить и использовать PHP в проекте. Но прежде чем начать, нужно понять, что такое PHP. Что такое
img
Как разработчик, вы знаете, что HTML расшифровывается как HyperText Markup Language (язык разметки гипертекста). HTML — это язык
img
Бесконечные споры вокруг искусственного интеллекта приводят к путанице. Существует много терминов, которые кажутся похожими, но
img
SVG расшифровывается как масштабируемая векторная графика. Это веб-дружелюбный векторный формат файлов, используемый для отображ
21 ноября
20:00
Бесплатный вебинар
Введение в Docker