Главная » Статьи » Худшая практика Javascript

Худшая практика Javascript

От автора: все говорят о «лучших практиках», которым нужно следовать в отношении различных технологий, и иногда мы склонны прислушиваться к этому совету. Однако как насчет того, что мы делаем не только неправильно, но и ужасно? Давайте кратко рассмотрим 5 худших вещей, которые вы можете сделать со своим кодом при написании JavaScript.

Использование правил неявного приведения типов

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

Учтите, что приведение типов иногда не совсем понятно даже для опытных разработчиков. Приведение типов, если вы об этом не знаете, — это механизм, с помощью которого JS пытается принудительно использовать типы переменных, когда вы используете несколько типов вместе, и они не особенно совместимы.

Например, оператор «+» легко использовать как оператор между двумя числами, не так ли? Два числа складываются, две строки объединяются, но что произойдет, если одно из них будет числом, а другое — строкой? Существуют правила, которые определяют, что происходит с каждым типом при использовании с другим.

Это правила приведения типов, и как только вы их узнаете, вы сможете ими воспользоваться. Вопрос в том, должны ли вы использовать их? Потому что не все полностью осознают, что 2 + ’2′ не будет 4. И что типом [] + 1 является ни число, ни массив.

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

Другой очень распространенный оператор, на который очень сильно влияет приведение типов, это оператор «= =». Мы часто используем его, но мы должны помнить, что с двумя знаками равенства движок JS будет пытаться принудительно заставлять совпадать типы. Например, число 1 равно строке, «1″а пустой массив равен пустой строке. Однако я уверен, что, когда вы писали это выражение равенства, вы не надеялись, что эти результаты окажутся верными, не так ли? И если ответ — «мне все равно», посмотрите этот глупый пример:

let a = //? if (a == 1) { console.log("Adding 10 to ", a) a += 10; console.log("a: ", a);
} if (a > 2) { console.log("Forcing a to be 1 in a strange way", a) a = a - (a - 1) console.log("a: ", a);
}

Как вы думаете, что произойдет, если мы выполним приведенный выше код с числом, строкой или логическим значением?

Худшая практика Javascript

Худшая практика Javascript

Худшая практика Javascript

На первом изображении я присвоил для a число 1, затем я присвоил строку «1″ и, наконец, значение true. Не было ни синтаксической ошибки, ни ошибки выполнения. Обратите внимание, как оператор «+» имеет тенденцию превращать числа в строки при совместном использовании, а оператор «-» наоборот. Дело в том, что true концептуально отличается от 1 и выражение true == 1 не должно быть валидным. Вместо этого, чтобы избежать такой путаницы, рекомендуется как можно чаще использовать оператор строгого равенства: «===».

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

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

Взгляните на следующие примеры:

//given
let a = 100; //Instead of turning it a string by doing
let stringA = ''+a //you should do
let stringA = a.toString() //and if you want to go back to a number this is not ideal
let numberA = +stringA //instead you should do
let numberA = Number(a)

Вы также можете превратить любое другое значение в строку, вызвав метод toString. Да, он есть везде. Даже ваши пользовательские объекты неявно получат этот метод (который вы можете перезаписать, чтобы обеспечить более точное строковое представление ваших объектов). К сожалению, не существует метода «to [INSERTYPE]» для каждого типа, но если есть сомнения, используйте конструктор типа, подобно тому что я показал для Number, это будет хорошей подсказкой для того, кто будет читать ваш код.

Помните, что код должен бесперебойно работать на компьютере, но он также должен быть прочитан людьми!

Использование var в 2021 году

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

У старой доброй var было всего 2 области применения: функциональная или глобальная. Это означает, что все, что вы определили с ней внутри функции, было доступно для всей функции, независимо от того, где вы это объявили. Или это было бы глобально, если бы вы сделали это снаружи. Другое интересное поведение var заключается в том, что она не приведет к ошибке, если вы повторно объявите существующую переменную, что потенциально может привести к запутанной логике, если вы повторно объявите одну и ту же переменную дважды внутри одной и той же функции, выполняя разные действия с обоими версиями.

Текущая и более усовершенствованная альтернатива — использовать либо let либо const, используя второй вариант, для неизменяемых значений (т. е. констант). Основное преимущество let перед var заключается в том, что первая может похвастаться лексической областью видимости, что означает, что она будет доступна только в пределах того блока кода, в котором вы ее объявили. Учитывая, что блок кода — это все, что вы пишете между { и }, вы можете объявить переменные, которые находятся только внутри тела оператора IF. Или внутри цикла FOR. И вы всегда можете использовать одно и то же имя, не опасаясь проблем с несовпадением значений.

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

Непонимание различий между обычными и стрелочными функциями

В рамках своей работы я провожу технические собеседования с людьми, которые хотят присоединиться к компании, в которой я работаю. И хотя я сейчас не работаю с JS ежедневно, если вы добавите Node.js или JavaScript в свое резюме, я спрошу об этом. И есть 100% шанс, что я спрошу о различиях между обычными и стрелочными функциями.

И главное, что я вижу, это то, что многие их не знают! Синтаксис стрелок — это не синтаксический сахар! Идите в ногу со временем и читайте документацию!

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

Самые большие различия между стрелочными функциями и обычными функциями:

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

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

Стрелочные функции не являются допустимыми конструкторами. Вы не поверите — обычные функции «конструктивны». Это означает, что их можно вызывать с ключевым словом new и возвращать новый экземпляр этой функции. Почему это возможно? Потому что тогда, до ES6, у нас также было прототипное наследование в качестве основного способа работы с объектами, а функции (обычные функции) были для них конструктором. Однако теперь с классами это больше не является «подходящим вариантом», и стрелочные функции отражают это изменение. Стрелочные функции, хотя и вызываемые (как и обычные), не могут быть конструктивными, поэтому вы не можете поставить new перед их вызовом, это не сработает.

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

Вот и все.

Непонимание THIS

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

При использовании this следует помнить о двух вещах:

Все в JS — это объект. Это означает, что ваши функции являются объектами, и ваши методы тоже являются объектами.

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

Ключевое слово this ссылается на текущий контекст выполнения функции, из которой вы ее вызываете. Это означает:

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

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

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

Таковы правила, и, зная их и понимая тот факт, что вы также можете перезаписать значение this извне, вы можете делать некоторые интересные вещи. Например:

class Person { constructor(first_name, last_name) { this.f_name = first_name; this.l_name = last_name; } } function greetPerson() { console.log("Hello there ", this.f_name, this.l_name);
} let oPerson1 = new Person("Fernando", "Doglio");
let oPerson2 = new Person("John", "Doe"); greetPerson.call(oPerson1);
greetPerson.call(oPerson2);

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

Таким образом, используя метод call, который есть у каждой функции, вы можете вызвать ее, перезаписав ее контекст. Позволяя внешней функции действовать как метод класса Person.

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

Теперь вы должны понимать, что это такое ключевое слово this и как использовать.

Неправильное использование замыканий и (нежелательные) утечки памяти

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

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

Замыкание — это функция JS, которая определяется другой функцией. В этой ситуации новая функция «привязывается» к области видимости родительской функции, так что, если внутри первой есть код, который ссылается на переменные, определенные во второй, она все еще может ссылаться на нее даже после того, как последняя была уничтожена. Давайте посмотрим на пример:

function X() { let a = 10; return function() { console.log(a); }
} const myX = X();
myX(); //what do you think will happen here?

Функция X возвращает анонимную функцию, которая, в свою очередь, выводит содержимое «a» в терминал. Эта проблема? К тому времени, когда я выполняю эту анонимную функцию, «a» больше не существует. Или нет? Когда мы возвращаем эту новую функцию, ее область видимости возвращается вместе с ней, поэтому она все еще может получить доступ к «a» откуда бы мы ни вызывали ее.

Другая особенность замыканий заключается в том, что все они имеют одну и ту же область видимости, если они созданы внутри одной функции. Итак, если бы наша функция X создала несколько функций, все они имели бы одну и ту же область видимости, поэтому все они имели бы ссылку на «a». Теперь давайте продолжим эту концепцию:

function X() { let bigVariable = Array(1000000).join("*"); let Xstate = globalState().get('X') let fn1 = () { if(!XState) { console.log("There is nothing stored") } } globalState().set('X',{ state: bigVariable, logic: () => { console.log("This is my method") } })
}

Давайте разберем пример. Наша функция X:

Определяет очень большую строку.

Получает значения ключа X из глобального состояния.

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

Мы устанавливаем новое значение для ключа состояния X, которое ссылается на большую строку, которую мы определили в начале этой функции.

Помните, что я сказал раньше? Закрывает общую область видимости, поэтому и наш метод logic, и наша функция fn1 имеют одну и ту же область видимости, а последняя ссылается на более старое значение ключа X. Таким образом, каждый раз, когда мы вызываем эту функцию, ее значение будет сохраняться внутри новой переменной Xstate, и хотя мы будем перезаписывать его при вызове метода set в конце, переменная Xstate останется активной, потому что она используется в closure ( fn1). Эта большая строка будет продублирована в памяти во второй раз, когда мы вызовем нашу функцию X, и если мы продолжаем ее вызывать, она будет дублировать строку.

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

function myComponent() { let bigVariable = Array(1000000).join("*"); let [Xstate, setX] = useState() useEffect( function fn1() { if(!XState) { console.log("There is nothing stored") } }) setX({ state: bigVariable, logic: () => { console.log("This is my method") } })
}

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

Автор: Fernando Doglio

Источник: blog.bitsrc.io

Редакция: Команда webformyself.

Читайте нас в Telegram, VK, Яндекс.Дзен