DEV Community

Cover image for Understanding JavaScript type conversions
Anton Melnyk
Anton Melnyk

Posted on • Updated on

Understanding JavaScript type conversions

Introduction

Probably the most confusing part of JavaScript is how it works with types. A lot of weirdness can be achieved thanks to JavaScript being a flexible and forgiving language with a rich history. You've likely seen fun things like that:

(NaN+Object()["to"+String["name"]]["call"]())[11] // Produces letter "U" 😮
Enter fullscreen mode Exit fullscreen mode

The example above is too extravagant, but in general good developers should understand all the lowdowns of the programming language they are using.

Let's clear all the misconceptions about how and when Javascript converts types.

What types are in JavaScript?

Values in JavaScript are one of the next types:

// We can use typeof function to get the type of the value

typeof undefined // "undefined"
typeof 42 // "number"
typeof "hello" // "string"
typeof true // "boolean"
typeof { name: 'Jhon' } // "object"
typeof alert // "function"
typeof Symbol("id") // "symbol"

typeof null // "object"
Enter fullscreen mode Exit fullscreen mode

This should be pretty self-explanatory if you worked with JavaScript already.

The null value of course is not an object though. Its type is "null". Yet for the historical reasons typeof function returns "object" for the null value.

As JavaScript is a language with weak typing so it will try to do implicit conversions between the types when it happens. But, implicit is a dangerous word to use in a JavaScript world!

What is type conversion?

When operand or a function parameter doesn't have expected type...

3 > "3"  // Expects number, given string
3 + true // Expects number, given boolean
alert(3) // Expects string, given number
alert({ name: 'John' }) // Expects string, given object
Enter fullscreen mode Exit fullscreen mode

Javascript converts the value to the expected type following specific rules.

Let's examine each of the most possible of them that you can meet in the code:

String type conversion

String type conversion applies when the given value is expected to be a string. The most basic example is the alert function:

alert(3)    // 3 becomes "3"
alert(true) // true becomes "true"
alert(null) // null becomes "null"
Enter fullscreen mode Exit fullscreen mode

As you can see, string conversion happens as you would expect in an obvious way.

Number type conversion

Number type conversion can be met in the math expressions and comparisons. Here is where usually a lot of confusion comes from.

2 * "3" // 6
6 / "2" // 3
3 - "1" // 2

3 + "3" // "33" 🤬
Enter fullscreen mode Exit fullscreen mode

Excuse me? Yes! The + operator actually works a bit differently. If one of the operands is a string, then all other operands are converted to string too and it works like string concatenation, not like the math expression:

// One of the operands is string "2"
// JavaScript will convert every other operand to string too
1 + "2" + true // The result is "12true"
Enter fullscreen mode Exit fullscreen mode

In arithmetic expressions, boolean, null and undefined convert to a numbers as following:

1 + true      // true becomes 1, result is 2
1 + false     // false becomes 0, result is 1
1 + null     // null becomes 0, result is 1
1 + undefined // undefined becomes NaN, result is NaN
Enter fullscreen mode Exit fullscreen mode

That's it. No ✨ magic, only strict rules!

Boolean type conversion

This type of conversion happens in logical operations. It follows strict rules too yet they are mostly obvious:

  • 0, NaN, undefined, null, "" are converting to false
  • everything else, including objects, to true
if ("hello") // true
if (0)       // false
if ({})      // true
Enter fullscreen mode Exit fullscreen mode

Type conversions for objects

What JavaScript is going to do if it needs to convert an object to string or number? Let's see:

parseInt({}) // NaN (converted to number)
alert({})    // "[object Object]" (converted to string)
alert([])    // ""
Enter fullscreen mode Exit fullscreen mode

These are default converted values. You rarely would want to convert objects into primitives... Still, if your code needs a more meaningful conversion you would need to know how to set the conversion rules explicitly.

When converting the object type (not array), JavaScript tries to find and call three object methods:

  1. Call obj[Symbol.toPrimitive](hint) – the method with the symbolic key Symbol.toPrimitive.

  2. Otherwise if type of hint is "string" call obj.toString() and obj.valueOf(), whatever exists.

  3. Otherwise if type of hint is "number" or "default" call obj.valueOf() and obj.toString(), whatever exists.

The hint is a type of the primitive the object is going to be converted to.

As you can see, you will need to explicitly set Symbol.toPrimitive property for your objects in case you need meaningful visualisation of your object.

Symbol.toPrimitive

Let's create an object and set Symbol.toPrimitive property.

const obj = {
  name: "Default conversion"
}

const country = {
  name: "Estonia",
  population: 1291170,

  [Symbol.toPrimitive](hint) {
    // For string conversion
    if(hint == "string") {
      return `Country: ${this.name}, population: ${this.population}`
    }

    // Otherwise number conversion
    return this.population
  }
}

alert(obj) // "[object Object]"
alert(country) // "Country: Estonia, population: 1291170"
alert(country + 1) // 1291171
Enter fullscreen mode Exit fullscreen mode

Comparison and type conversion

There is two specific comparison rules.

  • When doing a non-strict comparison Javascript converts operands to numbers if operands have different type:
0 == "0"  // true. String is converting to a number
0 === "0" // false. Strict comparison compares types too!
"0" != "" // true. There isn't type conversion
Enter fullscreen mode Exit fullscreen mode
  • null == undefined! There isn't any type of conversion here and these values have a different types! Yet in non-strict comparison undefined equals null and undefined by design:
null == undefined // true. God bless JavaScript ❤️
Enter fullscreen mode Exit fullscreen mode

Conclusion

Here we described the main rules and approach in which JavaScript makes types conversions. If you carefully observe all these rules you will find that they are basically obvious in most cases. Anyway, in the real production code I would encourage you to avoid implicit type conversions and weird comparisons:

  • For example, values received from user input will be in the string type. Convert them to number explicitly before using it further:
// ... imagine we handled user input event
const money = parseInt(event.target.value);
alert(typeof money == "number"); // true. Now we can safely use money as a number
Enter fullscreen mode Exit fullscreen mode
  • Concatenate string using template literals instead + operator:
// Can be confusing to read because the result can vary depending on operands types
const result = one + two 

// Explicitly concatenated string
const text = `${one}${two}` 
Enter fullscreen mode Exit fullscreen mode
  • Use strict comparison for comparing values with different types to avoid implicit conversion to number:
const something = 0;
alert(something == false)  // true
alert(something === false) // false
Enter fullscreen mode Exit fullscreen mode

That's it! I hope you found this little guide helpful and now you can better understand types and implicit conversions in JavaScript.

Now you should be able to decifer the article image:

{} + {}    // NaN, because object is converting to NaN
[] + []    // "", because array is converting to ""
0 == "0"   // true, because "0" is converting to 0
0 == ""    // true, because empty string is converting to 0
"" != "0"  // true, because operands are the same type (string) and no conversion happens
Enter fullscreen mode Exit fullscreen mode

Happy coding! ❤️

Top comments (5)

Collapse
 
jamesthomson profile image
James Thomson

Great article. I couldn't stress this any more:

Use strict comparison for comparing values with different types to avoid implicit conversion to number

I'd say using strict comparison will help avoid 99% of bugs related to type.

The part on Symbol.toPrimitive I wasn't aware of. Very interesting!

Collapse
 
daniel13rady profile image
Daniel Brady • Edited

Use strict comparison for comparing values with different types to avoid implicit conversion to number

I'd say using strict comparison will help avoid 99% of bugs related to type.

Hmm, possibly 🤔 but I'm not sure I agree with these statements.

When the input types are the same, == and === are identical; it's when the types are different that the value of having both abstract and strict comparison really shines for us as programmers.

In my head, I choose which comparison I use by answering this question: "Do I care about equality, or is equivalence good enough?"

If I care about equality of two values, then I care about the types of those values. But I think the comparison between those values is not the right place to enforce that: if possible, I should prefer to defend against unequal types earlier in my code using a strategy like the one @pixelgoo suggests:

Values received from user input will be in the string type. Convert them to number explicitly before using it further

If I do that, then not only does it give me more confidence in any comparisons I perform between the values, but when I know the types of my data it gives me some guarantees about the behavior of other operations I might perform with it.

On the other hand, there are many times where I do not care about value types, where I only care about the values' logical equivalence.

One of the most common scenarios for me is a "value presence check:" often I care about whether a variable has a value, but not necessarily what that value is; in this case, I can ask x == null ? and not have to worry about whether x is actually undefined, because as the author has pointed out, undefined == null 🎉

So, to me, when I see the strict comparison being used, I think, "Okay, this code cares about types and for some reason could not guarantee they are the same by this point." Does that make sense?

Collapse
 
antonmelnyk profile image
Anton Melnyk

Thank you, glad you discovered something new!

Collapse
 
_nemio_ profile image
Юнышев Артём

Dude, thanks for article.
But check your "number conversion" part ;) It's all wrong.

Collapse
 
daniel13rady profile image
Daniel Brady

Very nice write-up, Anton. Thanks for sharing!