Оператор typeof в JavaScript частично не работает. Этот пост в блоге объясняет, как исправить это и как распространить его использование на объекты.
Тип против экземпляра
тип. Используйте typeof, чтобы узнать, является ли данное значение объектом или примитивом и, в последнем случае, каким типом примитива это является. Это дает
следующие результаты :
> typeof undefined
'undefined'
> typeof null // well-known bug
'object'
> typeof true
'boolean'
> typeof 123
'number'
> typeof "abc"
'string'
> typeof function() {}
'function'
> typeof {}
'object'
> typeof []
'object'
> typeof unknownVariable
'undefined'
typeof null, возвращающий «объект», явно является ошибкой. Это, вероятно, будет исправлено в ECMAScript.next, и тогда возвращаемое значение будет «нулевым».
случай. Используйте instanceof, чтобы определить, является ли объект экземпляром данного типа. instanceof всегда возвращает false для примитивных значений.
> {} instanceof Object
true
> (function() {}) instanceof Function
true
> true instanceof Object
false
Вне примитивов
Можно также утверждать, что для объекта typeof должен возвращать имя типа объекта. Давайте посмотрим на два возможных решения.
Получение имени типа: свойство [[Class]]
Спецификация ECMAScript 5 определяет внутреннее свойство [[Class]] следующим образом (8.6.2 «Внутренние свойства и методы объекта», таблица 8):
Строковое значение, указывающее специфицированную классификацию объектов.
Нет доступа к этому значению напрямую из JavaScript, однако Object.prototype.toString () использует его:
> {}.toString()
'[object Object]'
Объект, второе слово в квадратных скобках, происходит от [[Class]]. Первое слово всегда объект. Хотя toString () переопределяется в подклассах Object, вы можете применить Object.prototype.toString () в общем случае [2], чтобы вернуть исходную функциональность:
> [1,2,3].toString()
'1,2,3'
> Object.prototype.toString.call([1,2,3])
'[object Array]'
> /xyz/.toString()
'/xyz/'
> Object.prototype.toString.call(/xyz/)
'[object RegExp]'
В статье «
Исправление JavaScript-оператора typeof » (автор Angus Croll) использует этот факт для определения функции toType (), которая использует регулярное выражение для извлечения значения [[Class]] из квадратных скобок. Это работает следующим образом:
toType({a: 4}) // "object"
toType([1, 2, 3]) // "array"
(function() { return toType(arguments) }()) // "arguments"
toType(new ReferenceError()) // "error"
toType(new Date()) // "date"
toType(/a-z/) // "regexp"
toType(Math) // "math"
toType(JSON) // "json"
toType(new Number(4)) // "number"
toType(new String("abc")) // "string"
toType(new Boolean(true)) // "boolean"
Проблемы с этим подходом:
- Объединяет примитивы и объекты: имеет смысл использовать заглавные буквы, чтобы различать примитивы и объекты. Например:
> typeof "abc" 'string' > new String("abc") instanceof String trueПервая строка — примитив, вторая — объект. Первый начинается со строчной буквы, второй начинается с заглавной буквы.
- Тип нового ReferenceError () должен быть ReferenceError, а не Error. Значение [[Class]] для всех экземпляров ошибки всегда равно «Ошибка»:
> {}.toString.call(new ReferenceError()) '[object Error]' > {}.toString.call(new TypeError()) '[object Error]' > {}.toString.call(new Error()) '[object Error]' - То, как он обрабатывает аргументы и специальные глобальные объекты, (возможно) не оптимально. Является ли Math действительно экземпляром Math? Я бы сказал, что все эти глобальные объекты являются экземплярами Object. Чтобы проверить их, вы должны сравнить через ===:
if (someValue === Math) { ... } - Это не работает для примитивов. У них нет свойства [[Class]]. Если вы обычно вызываете toString () для них, они временно заимствуют значение своих типов обёрток объекта [3]:
> Object.prototype.toString.call(123) '[object Number]'
Получение имени типа: гибридный подход
Давайте напишем альтернативный «улучшенный typeof», называемый getTypeName (). Подход:
- Используйте typeof для примитивных значений, отличных от нуля.
- Вернуть ноль для нуля.
- Вернуть obj.constructor.name для любого объекта obj (включая функции!).
Вот как выглядит реализация (исправление IE через
Адама Тибора ):
function getTypeName(value) {
if (value === null) {
return "null";
}
var t = typeof value;
switch(t) {
case "function":
case "object":
if (value.constructor) {
if (value.constructor.name) {
return value.constructor.name;
} else {
// Internet Explorer
// Anonymous functions are stringified as follows: 'function () {}'
// => the regex below does not match (expects at least 1 char after 'function')
var match = value.constructor.toString().match(/^function (.+)\(.*$/);
if (match) {
return match[1];
}
}
}
// fallback, for nameless constructors etc.
return Object.prototype.toString.call(value).match(/^\[object (.+)\]$/)[1];
default:
return t;
}
}
Применяется к аргументам, которые мы ранее использовали для toType ():
getTypeName({a: 4}) // "Object"
getTypeName([1, 2, 3]) // "Array"
(function() { return getTypeName(arguments) }()) // "Object"
getTypeName(new ReferenceError()) // "ReferenceError"
getTypeName(new Date()) // "Date"
getTypeName(/a-z/) // "RegExp"
getTypeName(Math) // "Object"
getTypeName(JSON) // "Object"
getTypeName(new Number(4)) // "Number"
getTypeName(new String("abc")) // "String"
getTypeName(new Boolean(true)) // "Boolean"
Есть одно предостережение: вам все еще нужно использовать typeof для неизвестных переменных.
> getTypeName(unknownVariable)
ReferenceError: unknownVariable is not defined
> typeof unknownVariable
'undefined'
Наконец, если конструктор является анонимным, лучшее, что может сделать любая функция, это сообщить нам, что экземпляр является объектом:
> var Foo = function () {};
> var foo = new Foo();
> toType(foo)
'object'
> getTypeName(foo)
'Object'
Рекомендации
- Значения JavaScript: не все является объектом
- Производительность JavaScript: Array.prototype против []
- Значения JavaScript: не все является объектом