Check if value is a Symbol in JavaScript
Asked Answered
K

4

24

How can I check if a value is Symbol in JS?

I do not see a Symbol.isSymbol(x) method. My test of (x instanceof Symbol) does not seem to work either.

Kosse answered 28/9, 2017 at 22:27 Comment(2)
typeof x === 'symbol' worksFields
x instanceof Symbol doesn't work because x is a primitive. If you cast x to an object that will work ie Object(x) instanceof Symbol//true However the accepted answer is still the best approach as long as you don't need pre-ES6 support.Nucleotide
U
35

Check it with typeof:

typeof x === 'symbol'
Umiak answered 28/9, 2017 at 22:30 Comment(4)
yes thanks, I finally discovered that in this article keithcirkel.co.uk/metaprogramming-in-es6-symbolsKosse
I guess it's a primitive not an object, maybe why instanceof does not work?Kosse
Yes, it is a primitiveRank
Also, developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Rank
R
7

Updated 2022: Go with the accepted answer! If you're working in an environment so outdated that Symbol needs to be polyfilled, then you'll know that already. You'll be excruciatingly aware of it. You'll be haunted by it. Then, sure, use my answer. Otherwise don't bother. typeof x === 'symbol' is almost definitely all you need these days.


In ES 2015 and up, typeof x === 'symbol' is all that's needed. But it won't work if you're transpiling your code to ES 5.1 or earlier, even if you're using a polyfill for the Symbol builtin.

Every polyfill I've seen, including the babel-polyfill, implements Symbol as an object (i.e. typeof x === 'object') using a constructor function called Symbol. So in those cases you can check that Object.prototype.toString.call (x) === '[object Symbol]'*.

Putting it all together, then, we get:

function isSymbol (x) {
    return typeof x === 'symbol'
        || typeof x === 'object' && Object.prototype.toString.call (x) === '[object Symbol]';
}

*Note that I'm not using instanceof in the transpiled scenario. The problem with instanceof is that it only returns true for objects that were created within the same global context as the assertion being made. So if, say, a web worker passes a symbol back to your page, or symbols are passed between iframes, then x instanceof Symbol will return false! This has always been true of all object types, including the builtins. instanceof often works just fine, but if there's any chance of your code being in a "multi-frame" scenario as I've described, use with caution!

Roentgenograph answered 6/7, 2018 at 10:19 Comment(1)
It doesn't matter if x was created with a constructor function called Symbol. If it's pre-ES6 then Object.prototype.toString.call(x) will return '[object Object]'Nucleotide
N
1

The most simple way of testing for a symbol is indeed:

typeof value=="symbol"

But you may not want to rely on typeof because it will return "object" for symbols that have been cast to an object. Such symbols still function as symbols in every way and should not be dismissed.

Symbols that have been polyfilled for pre-ES6 browsers also return "object" when using typeof because the return type of "symbol" was not yet available before ES6.

In that case using instanceof might be your best bet:

Object(value) instanceof Symbol;// works in most cases

The above is faster than the last method below because of the extra overhead required for a try/catch. However instanceof fails in some cases such as on objects created in a different window or frame and therefore a different global environment.

Here is a method that solves all of the above problems. It works with symbol primitives, symbols cast as objects regardless global environment, as well as polyfilled Symbol designed to work in pre-ES6 JavaScript. The reason it works is because Symbol.prototype.toString() will throw if passed a value that is neither a symbol nor symbol object.

// Annoyingly complex, yet guaranteed to work

function isSymbol(value) {
    var isSymbol, type = typeof value;
    if (type=="object") try {
        Symbol.prototype.toString.call(value)// Throws if value is not a Symbol primitive or object
        isSymbol = 1;// This is only set if it didn't throw
    }
    catch(e) {
    }
    return type=="symbol" || !!isSymbol;// cast to boolean
}
Nucleotide answered 1/3 at 21:15 Comment(0)
T
-1

The most efficient way is to test the constructor of a value:

const result = (value && value.constructor === Symbol);
Tyrontyrone answered 12/5, 2021 at 9:43 Comment(6)
I assume typeof does this check under the hood along with 1 or 2 other checks?Kosse
typeof returns a string, which forces a string comparison between the value returned and the value you're testing upon (typeof x === 'symbol' will compare two strings), which is presumably slower than value.constructor === Symbol (i'd suggest that the latter compares two internal ids, probably numerical).Tyrontyrone
Just for fun, I ran a quick benchmark comparing the two approaches, and your one beats the accepted answer about 6 times out of 10. And there's really not much in it, it's a very close race each time. Not surprising, since typeof is heavily optimised while your solution is slowed by the need to check for truthiness and then access an object property. Ever-so-slightly faster when run ten million times (as I just did), but I'd argue that readability trumps that sort of tiny performance gain, and the average developer is more familiar with typeof than the constructor property.Roentgenograph
@daemonexmachina I did some benchmarking myself and was surprised to discover that you're completely right, at least with v8 (did the tests with nodejs). I guess there might be some dedicated optimization in place on expressions like typeof x === "knownTypeString". Another interesting discovery was that !value performs as fast as value !== null && value !== void 0 && value === value (the required minimum to access value's constructor property). I'd expect the type coercion to slow things down, but it seems to be well-optimized as well. Thanks for pointing this out!Tyrontyrone
The thing is it's not type coercion. typeof is an operator that takes anything and returns a string, rather than coerce it into one. Because it's a native operator, it doesn't need type coercion or any other language feature that we as JS devs have access to. The implementation details can be much closer to the metal than anything you or I could write in our JIT-compiled scripting language. V8, for instance, is written in C++.Roentgenograph
The type coercion I was talking about is the one happening with the !value expression.Tyrontyrone

© 2022 - 2024 — McMap. All rights reserved.