Плохие привычки кодирования, от которых нужно отказаться прямо сейчас
Все говорят много о «лучших практиках» использования различных технологий, и иногда мы склонны прислушиваться к этим советам, однако мало информации о неудачных примерах.
Давайте кратко рассмотрим 5 худших вещей, которые вы можете сделать со своим кодом при написании JavaScript.
Приведение типов может быть интересным, оно позволяет вам писать работающий код, не беспокоясь о типе данных. Это, конечно, весело, только если вы на самом деле знаете правила приведения типов в JavaScript наизусть.
Приведение типов — это механизм, в котором JS принудительно использует типы переменных (несколько вместе), которые не особенно совместимы.
Например, попытаться использовать +
оператор между двумя числами легко, верно? Два числа складываются, две строки объединяются, но что произойдет, если одно из них — число, а другое — строка? Некоторые правила точно определяют, что происходит с каждым типом при использовании с другим.
Это правила приведения типов, и как только вы их выучите, вы сможете воспользоваться ими. Вопрос в том нужно ли это.
Потому что не все полностью осознают, что 2 + '2'
не будет 4. И что тип [] + 1
не является ни числом, ни массивом.
У JavaScript есть замечательная возможность игнорировать типы при присвоении значений переменным, но это не означает, что мы должны полностью игнорировать типы. Иногда проверка типов или соблюдение определенного стандарта именования может помочь другим избежать случайного выполнения действительно странных операций. В противном случае синтаксический анализатор позволит вам сделать это, а движок сделает все возможное, чтобы предоставить какой-либо результат.
Другой очень распространенный оператор, на который очень сильно влияет приведение типов, это ==
оператор. Мы часто используем это, но мы должны помнить, что только с двумя знаками равенства движок JS попытается заставить типы для значений совпадать. Например, число 1 равно строке "1"
, а пустой массив равен пустой строке. Когда вы писали это выражение равенства, то надеялись, что эти результаты будут истинными, не так ли? И если ответ «Мне все равно», посмотрите на этот глупый пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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 a
, затем присвоили строку "1"
и, наконец, значение true
. Никогда не было ни синтаксической ошибки, ни ошибки времени выполнения. Двигатель сделал все, что мог. Обратите внимание +
, что оператор имеет тенденцию превращать числа в строки при совместном использовании, а -
оператор — наоборот. Дело в том, что true
концептуально это отличается от 1 и true == 1
не должно быть действительным. Вместо этого, чтобы избежать такого рода путаницы, рекомендуется как можно чаще использовать оператор строгого равенства:===
Этот оператор, среди прочего, будет учитывать тип сравниваемых значений. Так что это намного безопаснее и ведет к более надежной логике.
И когда дело доходит до приведения типов или ручного принудительного преобразования типа значения в другое, постарайтесь сделать это как можно более явным. Таким образом, любой, кто читает ваш код, получает синтаксическую подсказку о том, что происходит, вместо того, чтобы гадать, когда это происходит и почему.
Посмотрите на следующие примеры:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//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
, это будет ясным ключом к тому, кто читает ваш код, что вы выполняете преобразование. значение там.
Помните, код должен без проблем работать на компьютере, но он также должен быть прочитан людьми!
Вместо создания монолитных приложений сначала создайте независимые компоненты и объедините их в функции и приложения. Это ускоряет разработку и помогает командам создавать более согласованные и масштабируемые приложения.
Инструменты OSS, такие как Bit , предлагают отличный опыт разработчика для создания независимых компонентов и составления приложений. Многие команды начинают с создания своих дизайн-систем или микро-фронтендов с помощью независимых компонентов.
Попробуйте →
var
Если вы не работаете с конкретным клиентом, который использует сильно устаревший и больше не поддерживаемый движок JS, вам не нужно продолжать использовать его.
У старого доброго var
было всего 2 области видимости: функциональная или глобальная. Это означает, что все, что вы определили с ним внутри функции, было доступно для всей функции, независимо от того, где вы ее объявили. Или это было глобально, если вы делали это снаружи. Другое интересное поведение var
заключается в том, что это не приведет к ошибке, если вы повторно объявите существующую переменную, она просто ничего не сделает с ней, что потенциально может привести к запутанной логике, если вы повторно объявите одну и ту же переменную дважды внутри одной и той же функции, делая разные вещи с обоими версии.
Текущая и более совершенная альтернатива состоит в использовании либо let
или const
, при этом второй вариант используется для неизменяемых значений (т.е. констант). Основное преимущество let
over var
заключается в том, что первый теперь может похвастаться лексической областью видимости, что означает, что он будет доступен только в объявленном вами блоке кода. Учитывая, что блок кода — это то, что вы пишете между {
и }
, вы можете объявить переменные, которые живут только внутри тела оператора IF. Или внутри кода цикла FOR. И вы всегда можете использовать одно и то же имя, не опасаясь проблем с конфликтующими значениями.
Весь смысл отказа var
в let
том, чтобы дать вам, разработчику, более детальный контроль над тем, для кого вы определяете свои переменные. Имейте в виду, вы можете отлично кодировать var
прямо сейчас, и все будет работать, если вы не повторяете имена или непреднамеренно объявляете что-то глобальное, а затем ссылаетесь на это где-то еще, не объявляя его предварительно. Известно, что это большое ЕСЛИ, но это может случиться. Новое и блестящее let
позволяет полностью избежать этой проблемы.
Если вы укажете Node.js или JavaScript в своем резюме, у вас будут спрашивать об этом. И есть вероятность 100%, что спросят об отличиях обычных функций от стрелочных.
И главное, что многие люди их не знают! Синтаксис стрелки — это не синтаксический сахар. Нужно идти в ногу со временем и читать специальную литературу!
На самом деле есть несколько отличий, помимо синтаксиса, который вы используете для их определения, и вы должны знать их все наизусть на этом этапе.
Самые большие различия между стрелочными функциями и обычными функциями:
forEach
выполнения. Если внутри функции стрелки вы используете ключевое слово this
, вы ссылаетесь на контекст родительской функции. Это большая разница, потому что перед функциями стрелок вам нужно будет сохранить ссылку на this
перед вызовом функции обратного вызова, а внутри обратного вызова использовать новую ссылку, иначе this
внутри это будет совершенно новый контекст.arguments
специальной скрытой переменной. Внутри обычной функции вы можете вызвать переменную, которую вы никогда не определяли arguments
, которая содержит массивоподобный объект со всеми аргументами, полученными во время выполнения функции. Это замечательно, если вы хотите создать функцию, которая будет получать переменное количество атрибутов. Стрелочные функции не имеют к нему доступа, однако теперь, благодаря остальным параметрам, он также больше не нужен .new
ключевого слова и возвращать новый экземпляр этой функции. Почему это возможно? Потому что тогда, до ES6, у нас тоже было прототипное наследование как основной способ работы с объектами, а функции (обычные функции) были для них конструктором. Однако теперь с классами это уже не «путь», и стрелочные функции отражают это изменение направления. Стрелочные функции, хотя и вызываемые (как и обычные), не являются конструируемыми, поэтому вы не можете поставить a new
перед их вызовом, это не сработает.Ну вот, это большое «нет-нет», в котором вы больше не виноваты. Пожалуйста.
Если вы уже некоторое время работаете с JavaScript, вам, скорее всего, приходилось иметь дело с this
ключевым словом. И хотя это может быть не так болезненно, как раньше, потому что вы, вероятно, использовали стрелочные функции, вы уверены, что действительно понимаете, что это такое и как это работает?
При использовании следует помнить две вещи this
:
this
слово не относится к коду в определении класса. Вы можете использовать его где угодно, включая тела функций. Вы даже можете использовать его вне глобальной области видимости, и вы получите некоторые интересные результаты.Имея это в виду, this
ключевое слово ссылается на текущий контекст выполнения функции, откуда вы ее вызываете. Это означает:
this
.Таковы правила, и, зная правила и понимая тот факт, что вы также можете перезаписать значение this
вне всех этих мест, вы можете сделать некоторые интересные вещи.
Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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-функция, которая определяется другой функцией. В этой ситуации новая функция «связывается» с областью действия родительской функции, поэтому, если внутри первой есть код, который ссылается на переменные, определенные во второй, он все еще может ссылаться на нее даже после того, как последняя была уничтожена. Давайте посмотрим на быстрый пример:
1 2 3 4 5 6 7 8 9 10 11 12 |
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
. Теперь давайте разовьем эту концепцию немного дальше:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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
, и если мы будем продолжать вызывать ее, она будет продолжать ее дублировать.
После нескольких вызовов мы начнем наблюдать, как наша свободная память улетучивается. И ни на минуту не думайте, что замыкания — это вариант использования, с которым вы никогда не столкнетесь. Они более распространены, чем вы думаете, просто иногда мы не осознаем, что используем их.
Позвольте мне быстро переименовать несколько вещей в моем предыдущем примере:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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.
Так что будьте осторожны, когда имеете дело с функциями и переменными, которые они используют.
Будьте внимательны при написании кода, обращайтесь к рекомендациями выше.
Источник статьи: https://medium.com/