Распространенные ошибки новичков 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-нуба номер один:

  1. Не учитывается тип параметра

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 (и пару может сделать даже профессионал, если не будет осторожен). До скорого!

ссылки:

  1. Сильная и слабая типизация: https://en.wikipedia.org/wiki/Strong_and_weak_typing
  2. Нечеткое равенство и одинаковость: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
  3. Арифметические операторы: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators
  4. parseFloat: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseFloat
  5. isArray: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
  6. Логические операторы: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_Operators
  7. Jest: восхитительное тестирование Javascript: https://jestjs.io/
  8. Repl.it: https://repl.it/
  9. Сравнение операторов Ruby на равенство: http://www.zenruby.info/2016/05/ruby-operators-equality-comparison.html

сноски:

  1. Это шутка, на сайте medium.com нет рекламы. Я клянусь, что ни одна компания никоим образом не компенсировала мне рекламу в этой статье, и что я никоим образом не связан с Death Wish Coffee.