Есть несколько выражений, которые обычно встречаются в 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 обычно работает в общедоступной сети в ситуациях, когда размер файла имеет значение . Меньшие файлы быстрее загружаются и используют меньшую полосу пропускания, и небольшие сочетания клавиш могут действительно дополнять.
Использование преимуществ более коротких выражений — это не оптимизация как таковая, это просто стиль кодирования, который использует все возможности языка.