Распространенные ошибки новичков JS с приведением типов и как их избежать
Краткое предисловие:
В последнее время в Vets Who Code наш главарь Jerome Hardaway публиковал небольшие задачи по коду, чтобы каждое утро разогревать всех и готовить наши новые войска к когорте, которая начинается в сентябре.
Это не сложные задачи, просто кое-что, что разбудит мозг каждого по утрам.
Однако мой мозг не любит просыпаться по утрам. Я работаю из дома и, как правило, работаю допоздна, в предрассветные часы. Чаще всего я встаю примерно за час до первой утренней встречи.
К тому времени, когда начинается стендап, мой мозг все еще пытается усвоить кофеин из первых двух чашек Death Wish Coffee (не платная реклама, мне просто нравится их высокооктановый напиток, и они заслуживают огласки).
Разбудите мой мозг раньше, чем он готов, и моя расшатанная задница одержима желанием порвать дерьмо. Я знаю, что это противоречит лучшим практикам работы дома. Я смирился с тем, что, как и многие другие, изо всех сил стараюсь поддерживать дисциплину и мотивацию, необходимые для поддержания такого образа жизни. Я работаю над этим, но бывают дни, когда я еще не совсем готов.
Именно в таком состоянии я начал замечать некоторые распространенные ошибки, которые проявлялись довольно часто. представлений. Имейте в виду, что люди, которые делают эти материалы, еще не начали нашу программу, они ждут своей очереди, поэтому я действительно не должен быть с ними так строг. Тем не менее, в их интересах изучить этот материал как можно раньше, поэтому, помня об этом, давайте начнем…
Пример проблемы:
Напишите функцию JS для вычисления суммы двух заданных чисел.
Если один из параметров или сумма обоих параметров равна 50, вернуть true
Звучит просто, не так ли?
На первый взгляд, что-то вроде этого может показаться сносным:
function fiftyOrSumOfFifty (num1, num2) {
if (num1 == 50 || num2 == 50) {
return true;
}
else if (num1 + num2 == 50) {
return true;
}
else {
return false;
}
}
Примечание: это не было взято из кодекса конкретного ученика, я не пытаюсь вызвать
кого-либо на квартердек. Скорее, это было самое распространенное
решение, которое я видел, немного подчищенное, с точки зрения форматирования… и это не удивительно, на самом деле. Учитывая тот факт, что у студентов практически не было предварительных инструкций, кроме предварительной работы, которую они сделали, чтобы быть принятыми в программу, я на самом деле очень рад видеть что-то подобное, даже с его проблемами.
Это даже работает, если вы подадите ему правильные параметры…
console.log(fiftyOrSumOfFifty(25, 25)) // returns `true`
console.log(fiftyOrSumOfFifty(25, 0)) // returns `false`
console.log(fiftyOrSumOfFifty(50, 0)) // returns `true`
console.log(fiftyOrSumOfFifty(4, 50)) // returns `true`
Однако, как я уже говорил вам ранее, сладость моих сливок и этот удивительный эффект кофеина (еще раз спасибо, Death Wish!) еще не преодолели горечь моего утреннего опьянения, и я решил начать кормить его параметрами, которые ему могут не понравиться. Это приводит к распространенной ошибке JS-нуба номер один:
- Не учитывается тип параметра
JS — язык со слабой типизацией. В отличие от строго типизированных языков, которые принудительно применяют тип разрешенных параметров, JS позволяет вам вставлять в параметры любые старые вещи и с радостью предоставит вам ответ, если это возможно. Однако этот ответ может быть не тем, который мы ищем. Например, что произойдет в приведенном выше коде, если мы передаем строку функции fiftyOrSumOfFifty
?
console.log(fiftyOrSumOfFifty('50', 0)) // returns `true`
// and...
console.log(fiftyOrSumOfFifty('5', '0')) // also returns `true`
console.log(fiftyOrSumOfFifty('5', 0)) // also returns `true`
Итак, вот что происходит: как я уже говорил, JS не волнует, если вы подадите ему неправильный тип параметров. Поскольку это язык со свободной типизацией, он примет что угодно в качестве параметра.
ЧТО-НИБУДЬ.
Вы можете передать ему целое число, число с плавающей запятой, строку, логическое значение, массив или вообще ничего (null
), если хотите, и если он сможет что-то сделать с этим параметром, который вписывается в код, который вы написали, JS что-то вернет. , не выдавая ошибки. В первом случае выше, когда мы отправляем строку, мы отправляем значение, которое может быть приведено к типу, который действительно может быть равен 50
, и поэтому возвращает true. На самом деле это приводит к следующей распространенной ошибке, и мы вернемся к ней через секунду. Это связано с этим, но гораздо более конкретным образом. В третьем примере строки мы отправляем '5'
, строку, и 0
, целое число. Почему это возвращает true
? Потому что вы можете использовать оператор +
для строки, и он будет работать.
Он просто не суммирует два параметра.
Когда вы используете оператор +
для пары строк, он объединяет строки; то есть он приклеивает второй прямо к хвостовой части первого. В примере функции это означает, что в конце концов, поскольку ни один из параметров не эквивалентен 50
, параметры передаются операции суммирования, которая, поскольку мы предоставили строку для одного из параметров, теперь становится операцией конкатенации, и '5' + 0
оценивается как '50'
.
Почему во втором примере это работает так же, как и в третьем, где мы отправляем два параметра разных типов? Поскольку JS использует трюк под названием
приведение типов... если в операции типа +
будут переданы две переменные разных типов, он попытается преобразовать одну из переменных в первый тип, который делает смысле, что в данном случае означает, что оба они обрабатываются как строки, и 0
прикрепляется к '5'
как '0'
, в результате чего получается '50'
, которое возвращает true
.
Но подождите... если оператор +
преобразовал его в строку, то почему это'50'
(строка, то есть текст) оценивается как равное 50
(число)?
Это не должно быть!
Ну и да, и нет… Я к этому иду, перестань быть таким нетерпеливым.
Во-первых, помните, как я говорил вам, что вы также можете добавить массив в параметры? Что ж, один из способов обойти бит конкатенации всей строки, о котором я упоминал ранее, - это проанализировать ваши параметры как числа с плавающей запятой (ex: parseFloat('15') // returns 15
)... когда вы делаете это с Array
, однако происходит кое-что интересное:
console.log(parseFloat(['50', 25])) // returns... 50??? WTH!!!
Когда вы используете parseFloat
для Array
, он анализирует только первый элемент в Array
! Странности никогда не прекращаются? Это меня удивило, и я только что обнаружил эту странность, когда печатал эту статью, так как я, честно говоря, никогда раньше не думал пытаться анализировать Array
как Float
. На самом деле это настолько неслыханно, что это даже не упоминается в документации MDN. Просто показывает, что каждый день можно узнавать что-то новое. Я решил вообще избежать этой проблемы, и если бы какой-либо параметр был массивом (ex: Array.isArray(num1)...
), я бы просто вернул false
.
В любом случае, возвращаясь к принуждению типов…
2. === > ==
Пример функции оценивает равенство с помощью оператора ==
(двойное равенство), который выводит typee. Это означает, что подобно тому, как оператор +
принуждает тип переменной и изменяет ее функцию с 'sum' на 'concatenate', оператор двойного равенства просматривает две var и говорит себе: Эй, один из этих вещей не похожа на другую... а может быть она могла бы быть, если бы они были одного типа? Давайте попробуем!
Поскольку '50'
может быть проанализировано как 50
, '50' == 50
оценивает true
. На самом деле это довольно простая ошибка, особенно если вы переходите на JS с другого языка, поскольку большинство других языков программирования не делают этого с помощью своего оператора двойного равенства — оператор ==
в большинстве других языков будет работать так же, как ===
. (triple-equals) делает в JS и будет выполнять строгую оценку эквивалентности... то есть строка НИКОГДА не будет равна числу, независимо от того, насколько похожим может быть текст внутри строки. В других языках оператор ===
не обязательно работает так же...
# For instance, in Ruby, `===` can be used to compare type
String === 'bacon' # evaluates to `true`
…и поэтому новичок в JS, перейдя с Ruby, может не ожидать, что он будет работать
так же, как в JS, или даже не знать о его существовании. В Ruby ==
делает то, что ===
делает в JS. Извинительно, если вы новичок в этом языке, но использование ==
для проверки эквивалентности — это привычка, от которой следует быстро отказаться при обучении новых разработчиков JS, чтобы они не приобрели плохую привычку. Использование этого оператора может вызвать слишком много побочных эффектов. Вместо использования ==
при проверке равенства следует использовать ===
(тройное равенство), так как он не выполняет никакого преобразования типов.
Говоря о вредных привычках, от которых следует быстро избавиться:
3. Чрезмерное количество условных выражений и возвратов, или «Сбой рефакторинга»
Это можно объяснить отсутствием опыта. Рефакторинг кода, чтобы сделать его более эффективным, со временем становится привычкой, но этому нужно учиться и применять его самостоятельно. В данном примере используется один if
для проверки условия и возврата true, если это условие возвращает true, затем используется else if
для проверки другого оператора и возврата true
, если этот оператор возвращает true
, затем, наконец, используется оператор else
для возврата false
, если все остальное терпит неудачу. Подумайте об этом предложении на мгновение. Сами условия уже возвращают логическое значение. Нам вообще не нужно условное выражение. С применением оператора ||
(логическое «или) мы можем сделать return (condition1) || (condition2) || (condition3)
и вообще избежать использования условных операторов.
Подведение итогов:
Вот мое первое решение проблемы; он позволяет строковым параметрам, если они могут анализировать значения с плавающей запятой и передавать условия, возвращать true
. Я решил сделать это, потому что JS в первую очередь предназначен для Интернета, и если вы выполняете манипуляции с DOM, это весь текст, пока вы не проанализируете его, чтобы он был иным (или, я думаю, не используете числовой ввод; если вы хотите всю правду, Я переусердствовал). Однако он не позволит массивам, undefined
или null
или любому другому типу данных, о котором я думал, кроме чисел и строк, получить значение true:
const numOrSumFifty = (num1, num2) => !anyArr(num1, num2) && any50(num1, num2)
const any50 = (num1, num2) => { return numIs50(num1) || numIs50(num2) || numIs50(sum(num1, num2)) }
const anyArr = (num1, num2) => Array.isArray(num1) || Array.isArray(num2)
const numIs50 = num => parseFloat(num) === 50
const sum = (num1, num2) => parseFloat(num1) + parseFloat(num2)
Однако, если бы я решил не разрешать использование строковых параметров (что, вероятно, следовало бы сделать в первую очередь; в конце концов, чрезмерное проектирование само по себе является анти-шаблоном), эта функция могла бы легко свестись к чему-то очень, очень многому. проще:
const numOrSumFifty =
(num1, num2) => num1 === 50 || num2 === 50 || num1 + num2 === 50
Помните, я говорил о провале рефакторинга? Всегда проверяйте себя, дети.
Я написал свои функции в repl.it как примеры Jest, а не простые примеры JS, поэтому я мог написать наборы тестов для функций (вы можете найти первый здесь, а no-string-params пример «здесь). Предлагаю вам написать свои тесты для сьютов в форке и попробовать сломать мои функции. Если вы можете придумать случай, которого у меня нет, я буду более чем счастлив обновить свой код и поблагодарить вас за находку; Я люблю экспертную оценку! Никто из нас не безупречен, и если я сделал что-то не так, я лучше узнаю и смогу это исправить, чем останусь в неведении.
Надеюсь, я пролил немного света на некоторые ошибки, которые обычно совершает новичок JS-разработчика с небрежной типизацией в Javascript (и пару может сделать даже профессионал, если не будет осторожен). До скорого!
ссылки:
- Сильная и слабая типизация: https://en.wikipedia.org/wiki/Strong_and_weak_typing
- Нечеткое равенство и одинаковость: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
- Арифметические операторы: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators
- parseFloat: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat
- isArray: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
- Логические операторы: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators
- Jest: восхитительное тестирование Javascript: https://jestjs.io/
- Repl.it: https://repl.it/
- Сравнение операторов Ruby на равенство: http://www.zenruby.info/2016/05/ruby-operators-equality-comparison.html
сноски:
- Это шутка, на сайте medium.com нет рекламы. Я клянусь, что ни одна компания никоим образом не компенсировала мне рекламу в этой статье, и что я никоим образом не связан с Death Wish Coffee.