В этой серии статей мы говорили о приведении типов, о том, как оно отличается от преобразования типов и как оно работает в динамически типизированных языках.
Если вы просто присоединяетесь к серии, ознакомьтесь с предыдущими статьями, чтобы убедиться, что вы в курсе того, где мы сейчас находимся:
В то время как другая статья была посвящена слабо типизированным языкам и типам данных на высоком уровне, мы рассмотрим некоторые конкретные примеры приведения типов в слабо типизированном языке и подводные камни, с которыми мы можем столкнуться, не зная, как работает приведение типов и как это может иметь неприятные последствия.
В частности, мы рассмотрим несколько примеров с использованием JavaScript, и хотя результаты, которые вы можете увидеть при использовании примеров, не обязательно будут переводить 1: 1 на другие языки, он все равно предоставит набор тестов, которые вы можете выступать во всем, что вы используете в своих повседневных или сторонних проектах, и оценивать результаты, которые вы видите.
Правила сравнения
Возможно, одна из самых распространенных проблем, возникающих в слабо типизированных языках, возникает всякий раз, когда мы проводим сравнения. Конечно, бывают и другие случаи, когда ожидание того, что переменная будет одного типа, а на самом деле это другой тип, может негативно повлиять на нас, но наиболее распространенные проблемы возникают, когда мы проводим какое-то сравнение.
Эти сравнения могут прийти в форме операций равенства, условных операций, побитовых операций или во время операций switch/case
.
Как упоминалось в предыдущих статьях этой серии, разные языки по-разному используют методы принуждения типов данных, поэтому примеры, которые мы рассматриваем в этой статье, могут немного отличаться в работе, которую вы выполняете.
То есть, мы будем смотреть на эти примеры с использованием JavaScript, так как это такой широко используемый язык, но правила по-прежнему применимы к другим языкам — просто другие языки могут устанавливать другой приоритет для одного типа данных над другим, поэтому результаты принуждения могут быть немного другими.
Итак, с учетом сказанного, давайте начнем с рассмотрения того, как JavaScript обрабатывает сравнения между типами данных с использованием оператора равенства ( ==
), оператора строгого равенства ( ===
) и при использовании значений, таких как undefined
и null
null
,
Нулевой и неопределенный
Прежде чем рассматривать сравнения между различными типами данных, давайте на минутку отметим, что в JavaScript undefined
и null
— это два разных типа значений. Как будто этого недостаточно, это может стать еще более запутанным при сравнении двух значений.
Во-первых, обратите внимание на следующее:
- Если вы должны выполнить
typeof( undefined )
в консоли, то результат будетundefined
. - Если бы вы выполняли
typeof( null )
в консоли, то результатом был быobject
.
Затем, если бы вы объявили переменную без фактического присвоения ей значения, и вы должны были оценить ее тип, вы бы увидели undefined
.
1
2
3
4
5
6
7
|
/**
* Declare a variable ‘name’ but don’t assign it a value.
* Execute the result of ‘typeof’ on a console and you’ll
* be given ‘undefined.’
*/
var name;
typeof( name );
|
Далее, скажем, мы решили инициализировать переменную null
значением. Если бы вы оценили переменную с использованием typeof
вам бы дали object
результат.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
// First, we’ll declare the variable
var number;
/**
* At this point, if we were to evaluate the variable
* using typeof, then we’d be given ‘undefined.’
*/
// Now assign the variable a value of ‘null’
number = null;
/**
* And evaluate the variable.
* be returned the value of ‘object’.
*/
typeof( number );
|
Смешение? Напомним, что в JavaScript null
— это object
которого undefined — это его собственный тип — undefined
.
С учетом вышесказанного, теперь мы можем реально посмотреть, почему сравнения могут стать трудными, когда мы выполняем сравнения значений без явного знания их типа.
Сравнения
В этом разделе мы рассмотрим результаты сравнения значений разных типов, но посмотрим, как они сравниваются друг с другом с использованием сравнений на равенство и строгого сравнения на равенство.
Обратите внимание, что все примеры, которые мы перечисляем ниже, должны быть выполнены в консоли браузера, такой как Firebug или Chrome Developer Tools.
Для начала мы начнем с undefined
и null
.
1
2
3
4
5
6
7
|
// Returns true
undefined == null;
null == undefined;
// Returns false
undefined === null;
null === undefined;
|
Обратите внимание, что в первом случае оператор равенства возвращает значение сравнения после выполнения приведения типов. То есть интерпретатор делает лучшее предположение о том, что мы имеем в виду при выполнении этого сравнения.
Во втором случае мы используем оператор строгого равенства. В этом случае никакого принуждения типов не происходит. Вместо этого он принимает значения точно такими, какие они есть, и сравнивает их.
Далее, давайте посмотрим на объявление переменной, не присваивая ей значение, а затем запустим сравнение.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
// Declare the variable, don’t assign it a value
var example;
// When compared to undefined, returns true in both cases
example == undefined;
example === undefined
// When compared to null, returns true or false
example == null;
example === null;
// Assign a value to the variable
example = null;
// Now do a strict comparison
example === null;
|
Как видите, все становится немного сложнее, когда мы начинаем объявлять и сравнивать переменные со значениями или без них.
Как только мы начнем вводить строки, числа и логические значения, это может стать еще сложнее. Сначала давайте начнем со строк и цифр. Мы начнем с объявления переменной со строковым значением 42
и числа с 42
а затем проведем сравнение.
1
2
3
4
5
6
7
8
9
|
var sNumber, iNumber;
sNumber = ’42’;
iNumber = 42;
// Equality comparisons yields true
sNumber == iNumber;
// Strict comparison yields false
sNumber === iNumber;
|
Опять же, обратите внимание, что в первом случае интерпретатор пытается привести значения из переменных, а затем сравнить их. В первом случае это работает — мы сравниваем строковое значение 42
с числовым значением 42
, но когда мы используем сравнение строгого равенства и получаем false
.
Второй случай технически более точен, поскольку первое значение — это строка, а второе — число. Сравнение двух значений разных типов всегда должно давать ложь.
Хотя мы рассмотрели это в предыдущей статье, как насчет случая чисел и логических значений?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
var iNumber, bBoolean;
iNumber = 0;
bBoolean = false;
// Returns true
iNumber == bBoolean;
// Returns false
iNumber === bBoolean;
// Returns true
iNumber = 1;
bBoolean = true;
iNumber == bBoolean;
// Returns false
iNumber === bBoolean;
|
На этом этапе вы должны начать замечать закономерность: всякий раз, когда вы сравниваете значения разных типов, JavaScript может корректно приводить значения, но он дает наиболее точный результат, когда вы используете оператор строгого равенства.
Наконец, давайте рассмотрим пример, который объединяет строки, числа и логические значения.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
var sExample, iExample, bExample;
sExample = ‘1’;
iExample = 1;
bExample = true;
// Returns true
sExample == iExample;
// Returns false
sExample === iExample;
// Returns true
iExample == bExample;
// Returns false
iExample === bExample;
// Returns true
sExample == bExample;
// Returns false
sExample === bExample;
|
Обратите внимание, что это основные сравнения; однако, когда это делается в контексте if/else
или if/else if/else
вы видите, как это может нарушить поток управления через условное выражение.
Что-нибудь еще?
Обратите внимание, что при выполнении логических операций, таких как &&
и ||
а также побитовые операторы, такие как &
и |
что правила принуждения все еще применяются. Для этого вы должны убедиться, что при выполнении этих операций используйте значения одного и того же типа для получения наиболее точных результатов.
В противном случае принуждение может привести к ложному положительному результату или ложному отрицательному результату.
Вывод
Это завершает наш краткий, начинающий взгляд на типы данных и приведение типов в динамически типизированных языках. В конечном счете, эмпирические правила должны всегда использовать операторы строгого равенства и обеспечивать, чтобы переменные, с которыми вы работаете, были одного типа. Если вы не уверены, вы всегда можете явно конвертировать их, используя стратегии, которые мы описали ранее в этой серии.
В этой серии мы рассмотрели, как типы изменяются и ведут себя от строго типизированных языков до слабо типизированных языков. Мы рассмотрели разницу между приведением типов и приведением, а также рассмотрели некоторые потенциальные ловушки, которые могут привести к чрезмерной зависимости от интерпретатора или компилятора при выполнении сравнений.
Наконец, мы рассмотрели некоторые стратегии написания более защитного кода, убедившись, что у нас есть тип данных, который нам нужен, и как использовать операторы строгого сравнения, чтобы обеспечить получение необходимых результатов.
Как уже упоминалось ранее, мы использовали JavaScript для целей наших примеров в этой серьезной статье, но другие слабо типизированные языки подвержены тем же подводным камням. То, как они расставляют приоритеты в своих приведениях, различается, но стратегии, описанные в этой серии, должны помочь в написании более гибкого кода при работе на слабо типизированных языках.