Статьи

Улучшение JavaScript typeof оператора

Оператор 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'

Рекомендации

  1. Значения JavaScript: не все является объектом
  2. Производительность JavaScript: Array.prototype против []
  3. Значения JavaScript: не все является объектом

 

С http://www.2ality.com/2011/11/improving-typeof.html