Статьи

Автоматическое преобразование типов в реальном мире

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

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

Первое из этих выражений является простым условием if() :

 if(foo) { } 

Второе — это присвоение переменной с выбором возможных значений:

 var x = foo || bar; 

Если foo и bar в этих примерах являются логическими значениями, то выражения просты: первое условие выполняется, если foo true ; второе выражение назначает foo для x, если foo равно true , или для bar назначает x, если нет.

Но что, если они не являются простыми логическими значениями — что, если foo является объектом, строкой или неопределенным? Что если foo и bar — это разные типы данных? Чтобы понять, как будут оцениваться эти выражения, нам нужно понять, как JavaScript автоматически преобразует данные между типами.

Автоматическое преобразование типов

JavaScript — это «свободно типизированный» язык , который означает, что всякий раз, когда оператор или оператор ожидает определенный тип данных, JavaScript автоматически преобразует данные в этот тип. Оператор if() в первом примере ожидает логическое значение, поэтому все, что вы определяете в скобках, будет преобразовано в логическое значение. То же самое верно для операторов while() и do...while() .

Значения JavaScript часто называют «правдивыми» или «ложными», в зависимости от того, каким будет результат такого преобразования (т. Е. Истина или false ). Самый простой способ думать об этом так: ценность правдива, если не известно, что она ложная ; и на самом деле существует только шесть ложных значений:

  • false (конечно!)
  • undefined
  • null
  • 0 (числовой ноль)
  • "" (пустая строка)
  • NaN (не число)

Заметными исключениями являются "0" (строка ноль) и все типы объектов — которые являются правдивыми — и это включает в себя все примитивные конструкторы, что означает, что new Boolean(false) оценивается как true ! (Вроде сбивает с толку, но на практике вам никогда не нужно создавать примитивные ценности таким образом.)

Примечание. Сравнение двух значений Falsey не всегда приводит к ожидаемому результату, например (null != false) False (null != false) даже если оба значения являются ошибочными. Существуют довольно сложные алгоритмы, которые определяют, как работают оценки равенства, и их обсуждение выходит за рамки этой статьи. Но если вам интересны детали, взгляните на Алгоритм сравнения абстрактного равенства, который является частью ECMAScript 5.1 .

Условие Ярлык

Пример if() я показал вам в начале, преобразует его выражение в логическое значение, и, поскольку объекты всегда оцениваются как true а null оценивается как false , мы можем использовать подобное условие для проверки существования элементов DOM :

 var element = document.getElementById("whatever"); if(element) { //the element exists } else { //the element doesn't exist } 

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

Однако другие случаи не так ясны, как этот пример:

 function doStuff(foo) { if(foo) { ... } } 

Подобные условия часто используются для обозначения «если аргумент foo определен» , но есть несколько случаев, когда это не сработает, а именно, любые случаи, когда foo является значением Фолси. Так, если, например, это логическое false или пустая строка, условный код не будет выполнен, даже если определен foo .

Это то, что мы хотим вместо этого:

 function doStuff(foo) { if(typeof foo != "undefined") { ... } } 

Аргументы (и другие переменные), которые не были определены, имеют тип данных "undefined" . Таким образом, мы можем использовать компаратор typeof для проверки типа данных аргумента, и тогда условие всегда будет проходить, если foo определен вообще. Конечно, выражение if() все еще вычисляет логическое значение, но вычисляемое логическое выражение является результатом этого выражения typeof .

Ярлык Назначения

Во втором примере, который я показал вам в начале, используется логический оператор, чтобы определить, какое из двух значений должно быть присвоено переменной:

 var x = foo || bar; 

Логические операторы не возвращают логическое значение, но они все еще ожидают логическое значение, поэтому преобразование и оценка происходят внутри. Если foo оценивается как true то возвращается значение foo , в противном случае возвращается значение bar . Это очень полезно.

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

 element.onclick = function(e) { e = e || window.event; }; 

Таким образом, e оценивается как логическое значение, и это будет правдой (объект события), если поддерживается модель аргумента события, или она будет ложной (неопределенной), если нет; если это правда, то возвращается e , а если нет, то возвращается window.event .

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

 var target = e.target || e.srcElement || window; 

Таким образом, каждая из этих ссылок оценивается по очереди (слева направо), и будет возвращена первая, которая оценивается как true . Первый случай обрабатывает стандартную модель, второй — для Internet Explorer, а третий — для Internet Explorer, когда событие может srcElement в объекте window (у которого нет свойства srcElement ).

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

 function doStuff(foo) { foo = foo || "default value"; } 

Теперь, если вы точно знаете, что foo всегда будет либо строкой, либо неопределенной, и при условии, что пустая строка должна рассматриваться как неопределенная, тогда это выражение безопасно. Но если нет, его нужно будет переопределить на что-то более точное, например, вот так:

 function doStuff(foo) { if(typeof foo != "string") { foo = "default value"; } } 

Протестировав тип на соответствие "string" мы можем обработать несколько случаев — когда foo не определено, а также где оно неправильно определено как нестроковое значение. В этом случае мы также разрешаем вводить пустую строку, но если мы хотим исключить пустые строки, нам нужно добавить второе условие:

 function doStuff(foo) { if(typeof foo != "string" || foo == "") { foo = "default value"; } } 

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

 function doDateStuff(timestamp) { timestamp = timestamp || new Date().getTime(); } 

Это не получится, если на входе будет 0 — потому что ноль — это значение Фолси, но это также допустимая временная метка.

Общие принципы

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

Тем не менее, он скорее задает вопрос — если мы знаем, что явное тестирование с использованием typeof всегда безопасно, а иногда полагаться на автоматическое преобразование типов — тогда почему бы просто не быть явным все время ? Конечно, если единственная причина предпочтения более короткого синтаксиса состоит в том, что он быстрее печатается, то это ленивая и небрежная причина.

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

Использование преимуществ более коротких выражений — это не оптимизация как таковая, это просто стиль кодирования, который использует все возможности языка.