DEV Community


The divine coercion between TypeScript basic types, their wrappers, simple objects and classes 👀

noseratio profile image Andrew Nosenko Updated on ・4 min read

A disclaimer: this is coming from an OOP/C# person with a solid JavaScript background, learning TypeScript specifics as I get to code in it. You probably already know all of the below if you're a TypeScript hero.

I admin I should have thoroughly read the Handbook first, before starting coding in TypeScript, but who has time for that, right? 😎

Let me start with a simple question, could you predict the output from the following TypeScript snippet?

let s1: String = "s"; 
console.log(s1 instanceof String)
console.log(typeof s1)
console.log(s1.constructor === String)
Enter fullscreen mode Exit fullscreen mode

Here's a playground link for your convenience, and the output is:

Enter fullscreen mode Exit fullscreen mode

So, s1 is not an instance of String (despite typed so), its typeof type is "string" but its constructor is nevertheless String. Coming from C#, this makes a lot of sense... not 🙂

Now, try predicting this output:

let s2: String = new String("s"); 
console.log(s2 instanceof String)
console.log(typeof s2)
console.log(s2.constructor === String)
Enter fullscreen mode Exit fullscreen mode

Here's a playground link, and the output is:

Enter fullscreen mode Exit fullscreen mode

Now, s2 is an instance of String, its typeof type is now "object" but its constructor is still String.

Finally, this one (playground):

let s3: string = "s"; 
console.log(s3 instanceof String) // TypeScript error
console.log(typeof s3)
console.log(s3.constructor === String)
Enter fullscreen mode Exit fullscreen mode


Enter fullscreen mode Exit fullscreen mode

This might be confusing, but it makes sense if we recall that TypeScript doesn't have any runtime type system (unlike C#). It just emits JavaScript, using whatever nascent type system the current ECMAScript version of JavaScript has.

For example, the generated JavaScript for the first code snippet looks like this (compiled with tsc -t ESNext code.ts):

let s1 = "s";
console.log(s1 instanceof String);
console.log(typeof s1);
console.log(s1.constructor === String);
Enter fullscreen mode Exit fullscreen mode

As a C# person, I might expect TypeScript to turn let s1: String = "s" into let s1 = new String("s") in JavaScript, because I declared s1 to be of non-basic, class type String (rather than of basic, primitive value type string).

Well, this is not how it works. Why? I asked this question on StackOverflow, and I got an excellent answer:

It is specifically a non-goal of the TypeScript language design to "add or rely on run-time type information in programs, or emit different code based on the results of the type system". So any suggestion or expectation of this sort should be abandoned.

To dig a bit deeper, the same relationship is true for classes and simple anonymous objects in TypeScript (playground):

interface IC {
    s: string;

class C implements IC {
    s: string;
    constructor(s: string) {
        this.s = s;

const c1: C = new C("c1");
// true
console.log(c1 instanceof C); 

const c2: C = { s: "c2" };
// false
console.log(c2 instanceof C); 

const c3: IC = { s: "c3" };
// false
console.log((c3 as C) instanceof C) 
Enter fullscreen mode Exit fullscreen mode

This works the way it does because TypeScript is a "duck typing" language. Note the as operator? This is a "type assertion", not some kind of type conversion that would affect the generated code. We simply tell TypeScript that (to our best knowledge) the object has the expected set of properties and methods, to avoid compile-time errors. If we really need to verify this during runtime, we should be using "type guards" for that.

Thus, the lessons I've learnt are:

  • Reading "Advanced Types" from the Handbook is a must.

  • Leave your #C baggage behind the door. There is no added runtime type system in the JavaScript code that TypeScript emits, and it won't magically make instanceof work the way we might expect. A class object must be explicitly constructed with new MyClass() syntax for instanceof to work, and that's a feature of JavaScript, not something specific to TypeScript.

  • Use primitive basic types (string, boolean, number, object) everywhere in TypeScript, and avoid using their class wrappers (String, Boolean, Number, Object), as a general rule.

  • If you really need to test whether a particular variable or property contains a string (boolean, number), prop.constructor === String works consistently for basic and wrapper types. When it is true, you can go one step further to check if typeof prop === "string", to tell whether it's a basic type or a wrapper class.

Note that prop.constructor === String may not work across realms (e.g., iframes), but === "String" will.

I hope the above makes sense. Feel free to leave a comment here or on my Twitter if you disagree 🙂


Editor guide