Оператор 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: не все является объектом