Тёмная сторона JavaScript
typeof null
Это оффициально признанный баг 6, не фиксищейся для поддержки совместимости со старым кодом 1 2.
typeof null
возвращает "object"
, однако null не является объектом 6, и что хуже, не ведёт себя как объект.
typeof null // "object"
var z = null; // null
z.prop = 1 // TypeError: z is null
Если мы заглянем в код библиотеки underscore.js 5, то увидим такую функцию как isObject()
, как раз правильно проверяющую на тип объекта.
// версия 1.6.0 (2014)
_.isObject = function(obj) {
return obj === Object(obj);
};
// версия 1.8.3 (2015)
// соответствует lodash
_.isObject = function(obj) {
var type = typeof obj;
return type === 'function' || type === 'object' && !!obj;
};
_.isObject(null) // false
А также функцию isNull()
проверяющую на соответствие null
(тоже самое в lodash):
_.isNull = function(obj) {
return obj === null;
};
_.isNull(null) // true
_.isNull({}) // false
_.isNull(undefined) // false
_.isNull(NaN) // false
Что есть NaN?
Возможно вы не знаете, но NaN
является числом 3 (и не важно, что NaN акроним от Not a Number),
просто это число никогда не равно самому себе 157.
typeof NaN // "number"
NaN === NaN // false
В underscore.js есть пример проверки на NaN
5 (тоже самое в lodash):
_.isNaN = function(obj) {
return toString.call(obj) === '[object Number]' && obj !== +obj;
};
_.isNaN(NaN) // true
Есть также встроенная функция isNaN(), но она работает некорректно 7:
isNaN(NaN) // true
isNaN(undefined) // true
isNaN({}) // true
isNaN(new Date().toString()) // true
isNaN("words") // true
В новом стандарте ECMAScript 6 (Harmony) предложена новая функция Number.isNaN()
избавленная от этих недостатков. Вот её полифилл 8:
Number.isNaN = Number.isNaN || function(value) {
return typeof value === 'number' && isNaN(value);
}
Операторы сложения и вычитания
Преобразование типов в JavaScript реализовано по-разному для операторов сложения и вычитания:
[]+[] // ""
[]+{} // "[object Object]"
[]-[] // 0
[]-{} // NaN
var l = ['a','b','c']
var o = {'q':1, 'w':2, 'e':3}
l+l // 'a,b,ca,b,c'
l-l // NaN
o+o // '[object Object][object Object]'
o-o // NaN
var L = [22]
L-[] // 22
[]-L // -22
+L // 22
L+L // '2222'
L-L // 0
L+[] // '22'
-L // -22
100-L // 78
100+L // '10022'
Обратите внимание, что в некоторых случаях, когда массив является одним из операндов оператора вычитания, массив может быть преобразован к числу автоматически.
Арифметика null
Преобразования осуществляемые при арифметических операциях и сравнениях >
>=
<
<=
, и при проверке равенства ==
-- различны. Алгоритм проверки равенства для undefined и null в спецификации прописан отдельно 4. В нём считается, что они равны между собой, но эти значения не равны никакому другому значению 9.
Из-за этого null ведёт себя странно при сравнении:
> null >= 0
true
> null <= 0
true
> null > 0
false
> null < 0
false
> null == 0
false
А значение undefined вообще «несравнимо»:
> undefined > 0
false
> undefined == 0
false
> undefined < 0
false
> undefined >= 0
false
> undefined <= 0
false
Пустой блок кода
Пустой блок кода {}
в одних случаях может вести себя как null, а в других -- как "ничего", хотя сам по себе интерпритируется как undefined.
> undefined
undefined
> {}
undefined
> {}+1 // {} -- null?
1
> undefined+1
NaN
> {}+'1' // {} -- wtf??!
1
> undefined+'1'
"undefined1"
> {}+null // {} -- null?
0
> undefined+null
NaN
Обход массива
Существует несколько способов обойти массив. Одни из них обходят массив как массив, а другие как объект 10.
var list = []
list[0] = "x"
list[100] = "y"
list[10000] = "z"
> list.forEach(function(item) { console.log(item) })
x
y
z
> for (var i = 0; i < list.length; i++) { console.log(item) }
x
undefined
...
undefined
y
undefined
...
undefined
z
> for (var key in list) {
if (
// Здесь мы проверяем что ключ взят не из цепочки прототипов
list.hasOwnProperty(key) &&
// Тут, что это числовой ключ, поскольку, хотя разряженный
// массив практически не отличается от объекта мы по прежнему
// используем только числовые ключи
/^0$|^[1-9]\d*$/.test(key) &&
// Здесь мы проверяем что ключ не выходит за пределы допустимые
// для ключей массива 2^32 - 2. Это число, согласно спецификации,
// максимальный из возможных индексов массива
key <= 4294967294
) {
console.log(list[key])
}
}
x
y
z