Lepszy sposób na uzyskanie typu zmiennej JavaScript?

260

Czy istnieje lepszy sposób na uzyskanie typu zmiennej w JS niż typeof? Działa dobrze, gdy:

> typeof 1
"number"
> typeof "hello"
"string"

Ale jest to bezużyteczne, gdy próbujesz:

> typeof [1,2]
"object"
>r = new RegExp(/./)
/./
> typeof r
"function"

Wiem o tym instanceof, ale wymaga to wcześniejszej znajomości typu.

> [1,2] instanceof Array
true
> r instanceof RegExp
true

Czy jest lepszy sposób?

Aillyn
źródło
1
Do Twojej wiadomości, typeof new RegExp(/./); // "function"problem w Chrome wydaje się być rozwiązany w Chrome 14.
113716
Ostrzeżenie: jeśli minimalizujesz JS i używasz „obiektów niestandardowych”, takich jak klasy maszynopisu, niektóre z poniższych odpowiedzi kończą się podaniem zaciemnionej nazwy funkcji, takiej jak nzamiast oczekiwanej oryginalnej nazwy. np. constructor.namemoże dać ci nzamiast oczekiwanego pełnego imienia i nazwiska
Simon_Weaver

Odpowiedzi:

221

Angus Croll napisał ostatnio ciekawy wpis na blogu na ten temat -

http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/

Omawia zalety i wady różnych metod, a następnie definiuje nową metodę „toType” -

var toType = function(obj) {
  return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
}
ipr101
źródło
3
Ciekawa uwaga dodatkowa - może to potencjalnie uszkodzić miejsce, w którym obiekt „host” jest przekazywany do funkcji. Na ActiveXObjectprzykład wystąpienia programu Internet Explorer .
Andy E,
Jeśli utworzyłeś swój własny typ obiektu (dziedziczenie prototypowe), w jaki sposób możesz uzyskać, aby zwracał bardziej konkretną wartość niż „obiekt”? Ten poruszono także kwestię tutaj , ale nigdy nie był adresowany.
EleventyOne
1
Jeśli nazwa twojego typu zawiera: lub _, możesz rozszerzyć ten regex domatch(/\s([a-z,_,:,A-Z]+)/)
masi
6
Wyrażenie regularne musiałoby także zawierać liczby dla takich rzeczy Uint8Array. Zamiast tego rozważ bardziej ogólny, match(/\s([^\]]+)/)który powinien poradzić sobie ze wszystkim.
Lily Finley,
2
Sugerowałbym \wzamiast tego użyć operatora słowo…
kaiser
51

Możesz spróbować użyć constructor.name.

[].constructor.name
new RegExp().constructor.name

Podobnie jak w przypadku wszystkich skryptów JavaScript, ktoś ostatecznie niezmiennie wskaże, że jest to coś złego, więc tutaj jest link do odpowiedzi, która obejmuje to całkiem dobrze.

Alternatywą jest użycie Object.prototype.toString.call

Object.prototype.toString.call([])
Object.prototype.toString.call(/./)
Alex Turpin
źródło
10
namejest rozszerzeniem ECMAScript i nie będzie działać we wszystkich przeglądarkach.
Andy E,
16
Odpowiedź została wzniesiona ze względu na potwierdzenie podejrzeń, że wszystko, co zrobiono w javascript, jest w jakiś sposób złe.
liljoshu
1
ZŁO: jeśli minimalizujesz swój kod, constructor.nameprawdopodobnie będzie to coś w rodzaju nzamiast oczekiwanej nazwy obiektu. Uważaj więc na to - szczególnie jeśli tylko zmniejszasz produkcję.
Simon_Weaver
nulli undefinedniestety nie ma konstruktora.
Andrew Grimm,
Sam JavaScript jest zły. I głupie. Fakt, że na to pytanie nie ma dobrej odpowiedzi, jest tego żywym dowodem.
user2173353,
38

Dość dobrą funkcją przechwytywania typu jest ta używana przez YUI3 :

var TYPES = {
    'undefined'        : 'undefined',
    'number'           : 'number',
    'boolean'          : 'boolean',
    'string'           : 'string',
    '[object Function]': 'function',
    '[object RegExp]'  : 'regexp',
    '[object Array]'   : 'array',
    '[object Date]'    : 'date',
    '[object Error]'   : 'error'
},
TOSTRING = Object.prototype.toString;

function type(o) {
    return TYPES[typeof o] || TYPES[TOSTRING.call(o)] || (o ? 'object' : 'null');
};

Przechwytuje wiele prymitywów udostępnianych przez javascript, ale zawsze możesz dodać więcej, modyfikując TYPESobiekt. Pamiętaj, że typeof HTMLElementCollectionw Safari zgłosi function, ale zwróci typ (HTMLElementCollection)object

Nick Husher
źródło
31

Przydatna może być następująca funkcja:

function typeOf(obj) {
  return {}.toString.call(obj).split(' ')[1].slice(0, -1).toLowerCase();
}

Lub w ES7 (komentarz, jeśli dalsze ulepszenia)

function typeOf(obj) {
  const { toString } = Object.prototype;
  const stringified = obj::toString();
  const type = stringified.split(' ')[1].slice(0, -1);

  return type.toLowerCase();
}

Wyniki:

typeOf(); //undefined
typeOf(null); //null
typeOf(NaN); //number
typeOf(5); //number
typeOf({}); //object
typeOf([]); //array
typeOf(''); //string
typeOf(function () {}); //function
typeOf(/a/) //regexp
typeOf(new Date()) //date
typeOf(new Error) //error
typeOf(Promise.resolve()) //promise
typeOf(function *() {}) //generatorfunction
typeOf(new WeakMap()) //weakmap
typeOf(new Map()) //map

Dzięki @johnrees za powiadomienie mnie o: błędzie, obietnicy, funkcji generatora

Vix
źródło
Dzięki za to. Działa również w przypadku obiektów daktylowych:typeOf(new Date())
James Irwin
Ach, zapomniałem o tym - dziękuję, teraz jest uwzględnione.
Vix
1
ItypeOf(Promise.resolve())//promise typeOf(function *() {})//generatorfunction
johnrees
Nie mogę uzyskać tej odpowiedzi do pracy na maszynie. Daje błąd:[ts] Cannot redeclare block-scoped variable 'toString'
DauleDK
Nie jestem pewien co do TypeScript. Czy próbowałeś wersji ES7? Przyczyną może nie być toString, który nie został jawnie zadeklarowany?
Vix,
15

Możemy również zmienić mały przykład z ipr101

Object.prototype.toType = function() {
  return ({}).toString.call(this).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
}

i zadzwoń jako

"aaa".toType(); // 'string'
greymaster
źródło
6
Pamiętajcie, dzieci, manipulowanie obiektami bazowymi JavaScript może prowadzić do kompatybilności i innych dziwnych problemów. Spróbuj tego używać oszczędnie zarówno w bibliotekach, jak i większych projektach!
Alexander Craggs
9

funkcja jednej linii:

function type(obj) {
    return Object.prototype.toString.call(obj).replace(/^\[object (.+)\]$/,"$1").toLowerCase()
}

daje to taki sam wynik jak jQuery.type()

Yukulélé
źródło
3

Możesz zastosować Object.prototype.toStringdo dowolnego obiektu:

var toString = Object.prototype.toString;

console.log(toString.call([]));
//-> [object Array]

console.log(toString.call(/reg/g));
//-> [object RegExp]

console.log(toString.call({}));
//-> [object Object]

Działa to dobrze we wszystkich przeglądarkach, z wyjątkiem IE - wywołując tę ​​zmienną uzyskaną z innego okna, po prostu wypluje [object Object].

Andy E.
źródło
1
@Xeon Myślę, że nienawiść do IE to trochę za dużo. IE9 jest znacznie lepszą przeglądarką niż jej poprzednicy.
Aillyn
4
@ pessimopoppotamus, ale nikt, kto zaktualizowałby przeglądarkę do IE9, nie używa IE.
Alex Turpin,
1
IE 9 to krok we właściwym kierunku, IE 10 wygląda całkiem nieźle. Nawiasem mówiąc, błąd, o którym wspomniałem, był regresją w IE 9 - naprawiono go w IE 8.
Andy E
3

Moje 2 ¢! Naprawdę, jednym z powodów, dla których rzucam to tutaj, pomimo długiej listy odpowiedzi, jest dostarczenie trochę więcej all in onetypowego rozwiązania i uzyskanie informacji zwrotnej w przyszłości, w jaki sposób go rozwinąć, aby uwzględnić więcejreal types .

Z poniższym rozwiązaniem, jak wspomniano powyżej, połączyłem kilka znalezionych tutaj rozwiązań, a także wprowadziłem poprawkę do zwracania wartości jQueryna obiekcie zdefiniowanym przez jQuery, jeśli jest dostępny . Dołączam również metodę do natywnego prototypu Object. Wiem, że często jest to tabu, ponieważ może zakłócać inne takie rozszerzenia, ale pozostawiam to user beware. Jeśli nie podoba ci się ten sposób, po prostu skopiuj funkcję podstawową w dowolnym miejscu i zastąp wszystkie zmienne thisparametrem argumentu, aby przekazać (np. Argumenty [0]).

;(function() {  //  Object.realType
    function realType(toLower) {
        var r = typeof this;
        try {
            if (window.hasOwnProperty('jQuery') && this.constructor && this.constructor == jQuery) r = 'jQuery';
            else r = this.constructor && this.constructor.name ? this.constructor.name : Object.prototype.toString.call(this).slice(8, -1);
        }
        catch(e) { if (this['toString']) r = this.toString().slice(8, -1); }
        return !toLower ? r : r.toLowerCase();
    }
    Object['defineProperty'] && !Object.prototype.hasOwnProperty('realType')
        ? Object.defineProperty(Object.prototype, 'realType', { value: realType }) : Object.prototype['realType'] = realType;
})();

Następnie po prostu używaj z łatwością, tak:

obj.realType()  //  would return 'Object'
obj.realType(true)  //  would return 'object'

Uwaga: Istnieje 1 argument do przejedzenia. Jeśli jest bool true, wówczas zwrot zawsze będzie pisany małymi literami .

Więcej przykładów:

true.realType();                            //  "Boolean"
var a = 4; a.realType();                    //  "Number"
$('div:first').realType();                   // "jQuery"
document.createElement('div').realType()    //  "HTMLDivElement"

Jeśli masz coś do dodania, co może być pomocne, na przykład określenie, kiedy obiekt został utworzony za pomocą innej biblioteki (Moo, Proto, Yui, Dojo itp.). Zachęcamy do komentowania lub edytowania tego i kontynuowania. dokładne i precyzyjne. LUB przejdź do strony, GitHubktórą dla tego stworzyłem i daj mi znać. Znajdziesz tam również szybki link do pliku min cdn.

SpYk3HH
źródło
2
function getType(obj) {
    if(obj && obj.constructor && obj.constructor.name) {
        return obj.constructor.name;
    }
    return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}

W moich wstępnych testach działa to całkiem dobrze. Pierwszy przypadek wypisze nazwę dowolnego obiektu utworzonego za pomocą „nowego”, a drugi przypadek powinien przechwycić wszystko inne.

Używam, (8, -1)ponieważ zakładam, że wynik zawsze zaczyna się [objecti kończy, ]ale nie jestem pewien, czy to prawda w każdym scenariuszu.

mpen
źródło
2

Myślę, że najbardziej uniwersalnym rozwiązaniem tutaj jest sprawdzenie undefinedi nullnajpierw, a następnie po prostu zadzwonić constructor.name.toLowerCase().

const getType = v =>
  v === undefined
    ? 'undefined'
    : v === null
      ? 'null'
      : v.constructor.name.toLowerCase();




console.log(getType(undefined)); // 'undefined'
console.log(getType(null)); // 'null'
console.log(getType('')); // 'string'
console.log(getType([])); // 'array'
console.log(getType({})); // 'object'
console.log(getType(new Set())); // `set'
console.log(getType(Promise.resolve())); // `promise'
console.log(getType(new Map())); // `map'
Arsenowitch
źródło
O ile wiem - .constructor.namezawsze zawiera wartość ciągu. Dla undefined / null mamy odpowiednie ciągi zwracane przez funkcję.
Arsenowitch,
Dzięki - usuwam moje komentarze…
Bergi,
Ponieważ constructorjest to dziedziczna właściwość, powoduje to uszkodzenie obiektów utworzonych za pomocą Object.create(null). Wystarczająco proste, aby to naprawić, ale jak to nazwać? „[obiekt null]”?
traktor53
0

typeof warunek służy do sprawdzania typu zmiennej, jeśli sprawdzasz typ zmiennej w warunku if-else np

if(typeof Varaible_Name "undefined")
{

}
MzkZeeshan
źródło