Источник: http://bonsaiden.github.io/JavaScript-Garden/ru/#types
JavaScript имеет 2 различных способа сравнения значений объектов на равенство.
Оператор сравнения состоит из двух символов равенства: ==
Слабая типизированность языка JavaScript подразумевает приведение обеих переменных к одному типу для того, чтобы произвести сравнение.
"" == "0" // false
0 == "" // true
0 == "0" // true
false == "false" // false
false == "0" // true
false == undefined // false
false == null // false
null == undefined // true
" \t\r\n" == 0 // true
В таблице выше показаны результаты приведения типов и это главная причина, почему использование ==
повсеместно считается плохой практикой: оно приводит к трудностям в отслеживании ошибок из-за сложных правил преобразования типов.
Кроме того, приведение типов во время сравнения также влияет на производительность; например, строка должна быть преобразована в число перед сравнением с другим числом.
Оператор строгого равенства состоит из трёх символов равенства: ===
В отличие от обычного оператора равенства, оператор строгого равенства не выполняет приведение типов между операндами.
"" === "0" // false
0 === "" // false
0 === "0" // false
false === "false" // false
false === "0" // false
false === undefined // false
false === null // false
null === undefined // false
" \t\r\n" === 0 // false
Результаты выше более понятны и позволяют быстрее выявлять ошибки в коде. Это в определённой степени улучшает код, а также дает прирост производительности в случае, если операнды имеют различные типы.
Хотя оба оператора ==
и ===
заявлены как операторы равенства, они ведут себя по-разному, когда хотя бы один из операндов является Object
.
{} === {}; // false
new String('foo') === 'foo'; // false
new Number(10) === 10; // false
var foo = {};
foo === foo; // true
Здесь оба операнда сравниваются на идентичность, а не на равенство; то есть будет проверяться, являются ли операнды одним экземпляром объекта, так же как делает is
в Python и сравниваются указатели в С.
Крайне рекомендуется использовать только операторы строгого равенства. В случае, когда намечается преобразование типов, нужно сделать явное приведение и не оставлять их на совести языковых хитростей с преобразованиями.
typeof
Оператор typeof
(вместе с instanceof
) — это, вероятно, самая большая недоделка в JavaScript, поскольку, похоже, он поломан более, чем полностью.
Хотя instanceof
еще имеет ограниченное применение, typeof
на самом деле имеет только один практический случай применения, который при всём при этом не является проверкой типа объекта.
Значение Класс Тип
-------------------------------------
"foo" String string
new String("foo") String object
1.2 Number number
new Number(1.2) Number object
true Boolean boolean
new Boolean(true) Boolean object
new Date() Date object
new Error() Error object
[1,2,3] Array object
new Array(1, 2, 3) Array object
new Function("") Function function
/abc/g RegExp object (function в Nitro/V8)
new RegExp("meow") RegExp object (function в Nitro/V8)
{} Object object
new Object() Object object
В таблице выше Тип представляет собой значение, возвращаемое оператором typeof
. Как хорошо видно, это значение может быть абсолютно любым, но не логичным результатом.
Класс представляет собой значение внутреннего свойства [[Class]]
объекта.
Из спецификации: Значением [[Class]]
может быть одна из следующих строк: Arguments
, Array
, Boolean
, Date
, Error
, Function
, JSON
, Math
, Number
, Object
, RegExp
, String
.
Для того, чтобы получить значение [[Class]]
, необходимо вызвать метод toString
у Object.prototype
.
Спецификация предоставляет только один способ доступа к значению [[Class]]
— используя Object.prototype.toString
.
function is(type, obj) {
var clas = Object.prototype.toString.call(obj).slice(8, -1);
return obj !== undefined && obj !== null && clas === type;
}
is('String', 'test'); // true
is('String', new String('test')); // true
В примере выше Object.prototype.toString
вызывается со значением this
, являющимся объектом, значение [[Class]]
которого нужно получить.
typeof foo !== 'undefined'
Выше проверяется, было ли foo
действительно объявлено или нет; просто обращение к переменной приведёт к ReferenceError
. Это единственное, чем на самом деле полезен typeof
.
Для проверки типа объекта настоятельно рекомендуется использовать Object.prototype.toString
— это единственный надежный способ. Как показано выше в таблице типов, некоторые возвращаемые typeof
значения не определены в спецификации: таким образом, они могут отличаться в различных реализациях.
Кроме случая проверки, была ли определена переменная, typeof
следует избегать во что бы то ни стало.
instanceof
Оператор instanceof
сравнивает конструкторы двух операндов. Это полезно только когда сравниваются пользовательские объекты. Использование на встроенных типах почти так же бесполезно, как и оператор typeof
.
function Foo() {}
function Bar() {}
Bar.prototype = new Foo();
new Bar() instanceof Bar; // true
new Bar() instanceof Foo; // true
// Всего лишь присваиваем Bar.prototype объект функции Foo,
// но не экземпляра Foo
Bar.prototype = Foo;
new Bar() instanceof Foo; // false
instanceof
со встроенными типамиnew String('foo') instanceof String; // true
new String('foo') instanceof Object; // true
'foo' instanceof String; // false
'foo' instanceof Object; // false
Здесь надо отметить одну важную вещь: instanceof
не работает на объектах, которые происходят из разных контекстов JavaScript (например, из различных документов в web-браузере), так как их конструкторы и правда не будут конструкторами тех самых объектов.
Оператор instanceof
должен использоваться только при обращении к пользовательским объектам, происходящим из одного контекста JavaScript. Так же, как и в случае оператора typeof
, любого другого использования необходимо избегать.
JavaScript — слабо типизированный язык, поэтому преобразование типов будет применяться везде, где возможно.
// Эти равенства — истинны
new Number(10) == 10; // объект типа Number преобразуется
// в числовой примитив в результате неявного вызова
// метода Number.prototype.valueOf
10 == '10'; // Strings преобразуется в Number
10 == '+10 '; // Ещё чуток строко-безумия
10 == '010'; // и ещё
isNaN(null) == false; // null преобразуется в 0,
// который конечно же не NaN
// Эти равенства — ложь
10 == 010;
10 == '-10';
Для того, чтобы избежать этого, настоятельно рекомендуется использовать оператор строгого равенства. Впрочем, хотя это и позволяет избежать многих распространенных ошибок, существует ещё много дополнительных вопросов, которые возникают из-за слабости типизации JavaScript.
Конструкторы встроенных типов, например, Number
и String
ведут себя различным образом, в зависимости от того, вызываются они с ключевым словом new
или без.
new Number(10) === 10; // False, Object и Number
Number(10) === 10; // True, Number и Number
new Number(10) + 0 === 10; // True, из-за неявного преобразования
Использование встроенного типа, такого как Number
, в качестве конструктора создаёт новый экземпляр объекта Number
, но при использовании без ключевого слова new
функция Number
будет вести себя как конвертер.
Кроме того, присутствие литералов или переменных, которые не являются объектами, приведет к еще большему насилию над типами.
Лучший вариант — это явное приведение к одному из трех возможных типов.
'' + 10 === '10'; // true
Путём добавления в начале пустой строки, значение легко приводится к строке.
+'10' === 10; // true
Используя унарный оператор плюс, можно преобразовать значение в число.
Используя оператор not
(!
) дважды, значение может быть приведено к логическому (булеву) типу.
!!'foo'; // true
!!''; // false
!!'0'; // true
!!'1'; // true
!!'-1' // true
!!{}; // true
!!true; // true