Худшие практики в Javascript
Перевод: Fernando Doglio — Javascript Worst Practice
Все говорят о «лучших практиках», которым следует следовать в отношении различных технологий, и иногда мы склонны прислушиваться к этим советам. Однако как насчет того, что мы делаем и даже не осознаем, что это не только неправильно, но и ужасно?
В этой статье я хочу кратко рассмотреть 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); }
Как вы думаете, что произойдет, если мы выполним приведенный выше код с числом, строкой или логическим значением?
На первом изображении я присвоил 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. Да, он есть у всех объектов. Даже ваши настраиваемые объекты неявно получат этот метод (который вы можете перезаписать BTW, чтобы обеспечить более точное строковое представление ваших объектов). К сожалению, нет метода «to [INSERTYPE]» для каждого типа, но в случае сомнений используйте конструктор типа, который я показал для Number, это будет хорошей подсказкой для тех, кто читает ваш код, который вы приводите.
Помните, что любой код не только должен бесперебойно работать на компьютере, но он также должен быть прочитан и понят людьми!
Использование var в 2021 году
Если вы не работаете с определенным клиентом, который использует сильно устаревший и больше не поддерживаемый JS-движок, у вас нет реальных оправданий для использования var в 2021 году.
Старый добрый var имел только 2 области действия: функциональную или глобальную. Это означает, что все, что вы определили с ним внутри функции, было доступно для всей функции, независимо от того, где вы это объявили. Или это было бы глобально, если бы вы сделали это снаружи. Другое интересное поведение var заключается в том, что он не приведет к ошибке, если вы повторно объявите существующую переменную, он просто ничего не сделает с этим, что потенциально может привести к запутанной логике, если вы повторно объявите одну и ту же переменную дважды внутри одной и той же функции, выполняя разные действия с обоими версиями.
Текущая и более усовершенствованная альтернатива — использовать let или const, имея второй вариант, используемый для неизменяемых значений (то есть констант). Основное преимущество let против var заключается в том, что первый теперь имеет лексическую область видимости, а это означает, что он будет доступен только в пределах того блока кода, в котором вы его объявили. Учитывая, что блок кода — это все, что вы пишете между скобками { … }, вы можете объявить переменные, которые находятся только внутри тела оператора IF или внутри кода цикла FOR. И вы всегда можете использовать одно и то же имя, не опасаясь проблем с несовпадением значений.
Весь смысл отказа от var в пользу let заключается в том, чтобы дать разработчику, более детальный контроль над тем, как вы определяете свои переменные. Имейте в виду, что вы можете и сегодня использовать var, и все будет работать, пока вы не столкнетесь с повторением имен или непреднамеренно объявите что-то глобальное, а затем используете это в другом месте, предварительно не объявив. Я знаю, вероятность такого маловероятно, но такое может случиться. Новые функции позволяют полностью избежать этой проблемы.
Непонимание различий между обычными функциями и стрелочными функциями
В рамках своей работы я провожу технические собеседования с людьми, которые хотят присоединиться к компании, в которой я работаю. И хотя сейчас я не работаю с JS ежедневно, если вы добавите Node.js или JavaScript в свое резюме, я буду спрашивать об этом. И есть 100% шанс, что я спрошу о различиях между обычными функциями и стрелочными функциями.
И главное, что я вижу, это то, что многие их не знают! Синтаксис стрелок — это не просто синтаксический сахар! Идите в ногу со временем и не забывайте читать чертову документацию!
На самом деле существует несколько отличий синтаксиса, который вы используете для их определения, и на этом этапе вы должны знать их все наизусть. Они существуют уже 6 лет, поэтому, если вы не соответствуете стандартам, это ваша проблема.
Самые большие различия между стрелочными функциями и обычными функциями:
- У стрелочных функций нет собственного контекста. Это особенно важно, если они определены внутри другой функции. Например, как обратный вызов для выполнения forEach. Если внутри стрелочной функции вы используете ключевое слово this, значит, вы ссылаетесь на контекст родительской функции. Это нужно учитывать если вам нужно внутри функции callback ссылаться на ее контекст и для этого перед стрелочными функциями вам нужно будет сохранить ссылку на this, а в вызове функции callback использовать эту новую ссылку, иначе this внутри него будет совершенно новым контекстом.
- Стрелочные функции не имеют скрытых аргументов. Внутри обычной функции вы можете вызвать переменную, которую вы никогда не определяли,
arguments
, которая содержит объект, подобный массиву, со всеми аргументами, полученными во время выполнения функции. Это замечательно, если вы хотите создать функцию, которая бы получала переменное количество атрибутов. Стрелочные функции не имеют к нему доступа, однако теперь, благодаря параметрам rest, это тоже больше не нужно. - Стрелочные функции не являются допустимыми конструкторами. Хотите верьте, хотите нет, обычные функции являются конструкторами. Это означает, что их можно вызывать с помощью ключевого слова new и будет ожить возвращение нового экземпляр этой функции. Почему это возможно? Потому что тогда, до ES6, у нас также было прототипное наследование в качестве основного способа работы с объектами, а функции (обычные функции) были для них конструктором. Однако теперь с классами это больше не является «подходящим вариантом», и стрелочные функции отражают это изменение. Стрелочные функции, хотя и вызываемые (как и обычные), не могут быть конструкторами, поэтому вы не можете добавить new перед их вызовом, это не сработает.
- И, наконец, последнее незначительное отличие: стрелочные функции не допускают дублирования имен аргументов. Это приемлемо для обычных функций в нестрогом режиме. Однако это не сработает со стрелочными функциями, независимо от того, какой режим вы используете.
Не понимание что такое THIS
Если вы уже какое-то время работаете с JavaScript, скорее всего, вам приходилось иметь дело с ключевым словом this. И хотя это может быть не так болезненно, как раньше, потому что вы, вероятно, использовали стрелочные функции, уверены ли вы, что действительно понимаете, что такое this и как оно работает?
При использовании this
следует помнить о двух вещах:
- Все в JS — это объект. Это означает, что ваши функции являются объектами, и ваши методы тоже являются объектами.
- Ключевое слово this не относится к коду в определении класса. Вы можете использовать его где угодно, включая тело функций. Черт возьми, вы даже можете использовать его за пределами глобальной области видимости, и вы получите некоторые интересные результаты
Имея это в виду, ключевое слово 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 в терминал. Эта проблема? К тому времени, когда я выполняю эту анонимную функцию, его больше не существует. Или нет?
Ответ таков, потому что, когда мы возвращаем эту новую функцию, ее область видимости возвращается вместе с ней, так что она все еще может получить доступ, откуда бы мы ни вызывали ее. Это и есть основная особенность замыкания.
Другая особенность замыкания заключается в том, что все они имеют одну и ту же область видимости, если они созданы внутри одной функции. Итак, если бы наша функция X создала несколько функций, все они имели бы одну и ту же область видимости, поэтому все они имели бы ссылку на. Теперь давайте рассмотрим эту концепцию немного дальше:
Рассмотрим это подробнее.
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 останется активной, потому что она используется в замыкание (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 приложении , но надеюсь вы поняли идею. Я переименовал несколько вещей, и внезапно у нас утечка памяти в компоненте React.
Так что будьте осторожны, когда имеете дело с функциями и используемыми ими переменными.
Какие еще приемы, которые я не включил в список, вы считаете ужасными для разработчиков JavaScript? Что вы больше всего ненавидите? Оставляйте примеры в комментариях и давайте расширим этот список!
Отличная статья! Спасибо!
var let и const это языковые конструкции у которых совершенно разные цели.
var не только используется в 2021 году, но var -у нет замены в принципе в случаях когда Вы хотите писать высокопроизводительный код на JS. Потому что накладные расходы на обеспечение блочной области видимости, в рамках JS платформы оказались значимыми.
А в некоторых случаях и радикально меняющими в худшую сторону производительность. Читайте что такое TDZ, когда она появляется и что за этим следует.
Более того, если человек не понимает тонкостей, я бы ему рекомендовал использовать только var. Потому как используя Letи Const написать код, который будет работать в десятки раз медленнее точно такого же кода но с var — очень просто.
Не говоря уже о том, что при помощи var сделать все что может const и let можно, а при помощи let и const создать то что можно делать с var — нельзя.
var никогда не был, и не будет устаревшим или забытой возможностью. Это возможность языка со своими функциями замены которой в языке сейчас нет в принципе.
Ладно хуйней заниматься, вы что на js состыковку Шатлов на МКС реализовываете , что так заворачиваете по поводу этого? Пишите понятный поддерживаемый код и не парьте мозги другим людям )
И деньги верни за аудит который не стал делать, даун
Нарвался на этого умника на ютубе, разводила стремный
Значение this в стрелочной функции зависит не от того где она ВЫЗЫВАЕТСЯ, а от того где она создана. Цитатка из mdn
Вот из-за таких статей, умников, вся жизнь простых людей превращается в кошмар. Весь код перегружен, вес приложений растет непомерно, производительность такая, что требует только последних топовых компьютеров, да и в целом, с таким подходом топовая техника быстро превращается в медлительный хлам. Вместо того, чтобы создавать действительно рабочий код, который работает и там и там и там, заставляют пользователей обновлять технику, ОС и прочее, прочее. В угоду разработчикам, которые думать нихера не умеют, подавай им готовые функции и методы, которые за них сами все сделают, да в носу поковыряться и повыеживаться, что var нельзя использовать в наше время. Тьфу, тунеядцы. Собеседование он там проводит, барин. Реально нет слов. Никогда не пишу комменты, столько интересного в жизни, но тут не удержался, это просто трешь. Пиши программы как хочешь, для себя, но для людей делать, упаси боже, наплакались уже из-за таких, все хорошее померло. Пока такие у нас сидят в конторах, не видать нам космоса.