const str = 'Hello world!'
const num = 42
const boo = true

typeof str // ??
typeof num // ??
typeof boo // ??

const str = 'Hello world!'
const num = 42
const boo = true

typeof str // ??
typeof num // ??
typeof boo // ??

const str = 'Hello world!'
const num = 42
const boo = true

typeof str // ??
typeof num // ??
typeof boo // ??

str instanceof String  // ??


num instanceof Number  // ??


boo instanceof Boolean // ??

const str = 'Hello world!'
const num = 42
const boo = true

typeof str // ??
typeof num // ??
typeof boo // ??

str instanceof String  // ??
str instanceof Object  // ??

num instanceof Number  // ??
num instanceof Object  // ??

boo instanceof Boolean // ??
boo instanceof Object  // ??

TL: "In JavaScript, everything is an object"

Me: "Well... No, there are primitives"

TL: "..."

"I don't understand why

[] + [] == ""

"

"Well, it is explained in the specification"

"Maybe, but shouldn't it be         ?"

NaN

"OK... I'll do a talk about this"

Type coercion

Types

7 types in JavaScript

  • Undefined
  • Null
  • Boolean
  • String
  • Number
  • Symbol (since ES 6)
  • Object

An ECMAScript language type corresponds to values that are directly manipulated by an ECMAScript programmer using the ECMAScript language.

ecma-262 9.0 (ECMAScript 2018)

The ECMAScript language types are Undefined, Null, Boolean, String, Symbol, Number, and Object.

An ECMAScript language value is a value that is characterized by an ECMAScript language type.

6 primitive data types

  • Undefined
  • Null
  • Boolean
  • String
  • Number
  • Symbol (since ES 6)

Every other value will be of type Object

typeof operator

  • "undefined"
  • "boolean"
  • "number"
  • "string"
  • "symbol"
  • "object"
  • "function"

7 types... but not (exactly) the same ones

The null case

  • is a value
  • is a type...
  • but
  • Bug that may never be fixed
typeof null // "object"
(function () {
  var undefined = 42
  var foo

  console.log(foo === undefined, foo, undefined)
})()


(function (undefined) {
  var foo

  console.log(foo === undefined, foo, undefined)
})(42)
(function () {
  var undefined = 42
  var foo

  console.log(foo === undefined, foo, undefined) // false undefined 42
})()


(function (undefined) {
  var foo

  console.log(foo === undefined, foo, undefined) // false undefined 42
})(42)

Do NOT do this!

The undefined case

The undefined case

  • is a value
  • is a type
  • is also a valid identifier (is not a reserved word)
(function () {
  undefined = 42
  var foo

  console.log(foo === undefined, foo, undefined)
})()


(function () {
  'use strict'
  undefined = 42
  var foo

  console.log(foo === undefined, foo, undefined)
})()
(function () {
  undefined = 42
  var foo

  console.log(foo === undefined, foo, undefined) // true undefined undefined
})()


(function () {
  'use strict'
  undefined = 42 // TypeError
  var foo

  console.log(foo === undefined, foo, undefined)
})()

Do NOT do this!

(function (w, d, undefined) {
  // Code where undefined is the undefined value no matter what
})(window, document)
(function (w, d, undefined) {
  // Code where undefined is the undefined value no matter what
})(window, document)



(function ($, w, d, undefined) {
  // jQuery plugin code
})(jQuery, window, document)

jQuery? Seriously, man?

The Number cases

NaN  // Not a Number
0 and -0
Infinity and -Infinity
NaN  // Not a Number
0 and -0
Infinity and -Infinity

// Statement/Operation      Result        Note

typeof NaN                  "number"      // ...Yes, I know, right?

1/0                         Infinity
1/-0                        -Infinity

typeof Infinity             "number"
typeof -Infinity            "number"
      
NaN  // Not a Number
0 and -0
Infinity and -Infinity

// Statement/Operation      Result        Note

typeof NaN                  "number"      // ...Yes, I know, right?

1/0                         Infinity
1/-0                        -Infinity

typeof Infinity             "number"
typeof -Infinity            "number"


0 === -0                    true          // Wut?
NaN === NaN                 false         // Only value in ES that is not equal to itself
   
NaN  // Not a Number
0 and -0
Infinity and -Infinity

// Statement/Operation      Result        Note

typeof NaN                  "number"      // ...Yes, I know, right?

1/0                         Infinity
1/-0                        -Infinity

typeof Infinity             "number"
typeof -Infinity            "number"


0 === -0                    true          // Wut?
NaN === NaN                 false         // Only value in ES that is not equal to itself

isNaN(NaN)                  true          // OK
isNaN("foo")                true          // Wut?      
NaN  // Not a Number
0 and -0
Infinity and -Infinity

// Statement/Operation      Result        Note

typeof NaN                  "number"      // ...Yes, I know, right?

1/0                         Infinity
1/-0                        -Infinity

typeof Infinity             "number"
typeof -Infinity            "number"


0 === -0                    true          // Wut?
NaN === NaN                 false         // Only value in ES that is not equal to itself

isNaN(NaN)                  true          // OK
isNaN("foo")                true          // Wut?

Number.isNaN(NaN)           true          // Number.isNaN() appeared in ES2015 (ES6)
Number.isNaN("foo")         false  
NaN  // Not a Number
0 and -0
Infinity and -Infinity

// Statement/Operation      Result        Note

typeof NaN                  "number"      // ...Yes, I know, right?

1/0                         Infinity
1/-0                        -Infinity

typeof Infinity             "number"
typeof -Infinity            "number"


0 === -0                    true          // Wut?
NaN === NaN                 false         // Only value in ES that is not equal to itself

isNaN(NaN)                  true          // OK
isNaN("foo")                true          // Wut?

Number.isNaN(NaN)           true          // Number.isNaN() appeared in ES2015 (ES6)
Number.isNaN("foo")         false

Object.is(0, -0)            false         // Object.is() appeared in ES2015 (ES6)
Object.is(NaN, NaN)         true          

The Number cases

1 + 2 === 3 // ??
1 + 2 === 3 // true

0.1 + 0.2 === 0.3 // ??
1 + 2 === 3 // true

0.1 + 0.2 === 0.3 // false

0.1 + 0.2 // 0.30000000000000004
  • It is NOT only observed in JavaScript
  • Actually it is because of how floating-point numbers are represented in 64 bits
  • See IEEE 754 specs for more info
Math.pow(2, -52) // Epsilon
Math.pow(2, -52) // Epsilon
Number.EPSILON // ES6
Number.EPSILON === Math.pow(2, -52) // true
Number.EPSILON === 2**-52  // true

const isCloseEnough = (a, b) => ( Math.abs(a - b) < Number.EPSILON )
Math.pow(2, -52) // Epsilon
Number.EPSILON // ES6
Number.EPSILON === Math.pow(2, -52) // true
Number.EPSILON === 2**-52  // true

const isCloseEnough = (a, b) => ( Math.abs(a - b) < Number.EPSILON )
isCloseEnough(0.4 + 0.02, 0.42)  // true
Math.pow(2, -52) // Epsilon
Number.EPSILON // ES6
Number.EPSILON === Math.pow(2, -52) // true
Number.EPSILON === 2**-52  // true

const isCloseEnough = (a, b) => ( Math.abs(a - b) < Number.EPSILON )
isCloseEnough(0.4 + 0.02, 0.42)  // true
isCloseEnough(0.4 + 0.019999999999999, 0.42) // false
Math.pow(2, -52) // Epsilon
Number.EPSILON // ES6
Number.EPSILON === Math.pow(2, -52) // true
Number.EPSILON === 2**-52  // true

const isCloseEnough = (a, b) => ( Math.abs(a - b) < Number.EPSILON )
isCloseEnough(0.4 + 0.02, 0.42)  // true
isCloseEnough(0.4 + 0.019999999999999, 0.42) // false
isCloseEnough(0.4 + 0.0199999999999999, 0.42) // true

Boxing,

autoboxing and back to primitive

Boxing

const boxedStr = new String('Hello world!')
boxedStr instanceof String  // ??
boxedStr instanceof Object  // ??
typeof boxedStr // ??

const boxedNum = new Number(42)
boxedNum instanceof Number  // ??
boxedNum instanceof Object  // ??
typeof boxedNum // ??

const boxedBoo = new Boolean(true)
boxedBoo instanceof Boolean // ??
boxedBoo instanceof Object  // ??
typeof boxedBoo // ??
const boxedStr = new String('Hello world!')
boxedStr instanceof String  // true
boxedStr instanceof Object  // true
typeof boxedStr // "object"

const boxedNum = new Number(42)
boxedNum instanceof Number  // true
boxedNum instanceof Object  // true
typeof boxedNum // "object"

const boxedBoo = new Boolean(true)
boxedBoo instanceof Boolean // true
boxedBoo instanceof Object  // true
typeof boxedBoo // "object"

Autoboxing

42..toString() // ??
42..toString() // "42"

true.valueOf() // true

'Hello world!'.toUpperCase() // "HELLO WORLD!"
42..toString() // "42"

true.valueOf() // true

'Hello world!'.charAt(1) // ??
42..toString() // "42"

true.valueOf() // true

'Hello world!'.toUpperCase() // "HELLO WORLD!"



typeof 'Hello world!'.toUpperCase() // "string"

typeof (new String('Hello world!').toUpperCase()) // ??
42..toString() // "42"

true.valueOf() // true

'Hello world!'.toUpperCase() // "HELLO WORLD!"



typeof 'Hello world!'.toUpperCase() // ??
42..toString() // "42"

true.valueOf() // ??
42..toString() // "42"

true.valueOf() // true

'Hello world!'.toUpperCase() // "HELLO WORLD!"



typeof 'Hello world!'.toUpperCase() // "string"

typeof (new String('Hello world!').toUpperCase()) // "string"

Back ToPrimitive

const unboxedStr = String(boxedStr) // "'Hello world!"
const unboxedNum = Number(boxedNum) // 42
const unboxedBoo = Boolean(boxedBoo) // true

typeof unboxedStr // ??
typeof unboxedNum // ??
typeof unboxedBoo // ??
const unboxedStr = String(boxedStr) // "'Hello world!"
const unboxedNum = Number(boxedNum) // 42
const unboxedBoo = Boolean(boxedBoo) // true

typeof unboxedStr // "string"
typeof unboxedNum // "number"
typeof unboxedBoo // "boolean"

Coercion

  • "Explicit"

  • "Implicit"

  • Implicit

"Explicit" coercion

String(42)

Number('42')

Boolean(42)
String(42) // "42"

Number('42') // 42

Boolean(42) // true

"Explicit" coercion

Boolean('true') // ???

Boolean('false') // ???

Boolean(0) // ???

Boolean('') // ???

Boolean('0') // ???

Boolean({}) // ???

Boolean([]) // ???

"Explicit" coercion

Number(true) // ???

Number('') // ???

Number('\n\r\t ') // ???

Number(null) // ???

Number(undefined) // ???

Number({}) // ???

Number([]) // ???

"Explicit" coercion

String(4.2e1) // ???

String(0) // ???

String(-0) // ???

String({}) // ???

String([]) // ???

"Implicit" coercion

'' + 42    // ???

42 + ''    // ???

+'42'      // ??

+'4.2e1'   // ??

+null      // ???

+undefined // ???

Implicit coercion

40 + 2 + '' // ???

'' + 40 + 2 // ???

Implicit coercion

'true' == true // ???

'false' == false // ???

!'true' == true // ???

!!'true' == true // ???

Implicit coercion

[] + 42 // ???

40 + 2 + [] // ???

[] + 40 + 2 // ???

{} + 40 + 2 // ???

40 + 2 + {} // ???

Implicit coercion

if (42) {
 
}

if ('') {
 
}

if ('0') {
 
}

if ('') {
 
}
if (42) {
 
}

if ('') {
 
}

if ('0') {
 
}

if ('') {
 
}


if (Boolean(x) === true) {
 
}
if (42) {
 
}

if ('') {
 
}

if ('0') {
 
}

if ('') {
 
}


if (Boolean(x) === true) { // Please... don't do this
 
}

Implicit coercion

[] + {} // ???

[] == ![] // ???

{} + [] // ???

({}) + [] // ???

[] + [] // ???

[] + +[] // ???

[] ++[] // ???

{} + {} // ???

{foo: 'bar'} + [] // ???

({foo: 'bar'}) + [] // ???

1 - - + + + - + -41 // ???

1 - - + + + - + -'41' // ???

1 + '41' // ???

43 - '1' // ???

!!~'areyouthere'.indexOf('no')

ToPrimitive

Objects, including native objects

ToPrimitive

Abstract operation ToPrimitive takes an input argument and an optional argument PreferredType.

PreferredType can either be "string", or "number", and it defaults to "number"

if @@toPrimitive is present, use it, if it returns an object, throw TypeError

else if  PreferredType is "string", returns .toString() or .valueOf()*

else if  PreferredType is "number", returns .valueOf() or .toString()*

* The first method that returns a non-object value

ToPrimitive

if @@toPrimitive is present, use it, if it returns an object, throw TypeError

const foo = {
  [Symbol.toPrimitive]: () => 'foo object',
}
// Will be OK to convert to primitive

const bar = {
  [Symbol.toPrimitive]: () => ({foo: 'bar'})
}
// Will throw TypeError when trying to convert to primitive
const foo = {
  [Symbol.toPrimitive]: () => 'foo object',
}
// Will be OK to convert to primitive

const bar = {
  [Symbol.toPrimitive]: () => ({foo: 'bar'})
}
// Will throw TypeError when trying to convert to primitive



String(foo) // 'foo object'

String(bar) // TypeError: can't convert ({[Symbol.toPrimitive]() { return {foo: 'bar'}}})
            // to number: its [Symbol.toPrimitive] method returned an object

ToPrimitive

const foo = {
  valueOf: () => 42,
  toString: () => '42 in string',
}


String(foo) // '42 in string'

Number(foo) // 42


foo + 1 // ??

foo - 1 // ??

foo + '1' // ??
const foo = {
  valueOf: () => 42,
  toString: () => '42 in string',
}


String(foo) // '42 in string'

Number(foo) // 42


foo + 1 // ??

foo - 1 // ??

foo + '1' // ???

else if  PreferredType is "string", returns .toString() or .valueOf()*

else if  PreferredType is "number", returns .valueOf() or .toString()*

PreferredType can either be "string", or "number", and it defaults to "number"

const foo = {
  valueOf: () => 42,
  toString: () => '42 in string',
}


String(foo) // '42 in string'

Number(foo) // 42


foo + 1 // 43

foo - 1 // ??

foo + '1' // ??
const foo = {
  valueOf: () => 42,
  toString: () => '42 in string',
}


String(foo) // '42 in string'

Number(foo) // 42


foo + 1 // 43

foo - 1 // 41

foo + '1' // ??
const foo = {
  valueOf: () => 42,
  toString: () => '42 in string',
}


String(foo) // '42 in string'

Number(foo) // 42


foo + 1 // 43

foo - 1 // 41

foo + '1' // 421

ToBoolean

// Type        Result

Undefined    false

Null         false

Boolean      argument

Number       0, NaN are false, otherwise true

String       Empty string "" is false, otherwise true

Symbol       true

Object       true
// Falsy values

null
undefined
NaN
""
false
0
-0
// Falsy values                     // Truthy values

null                                Everything else
undefined
NaN
""
false
0
-0
// Type        Result

Undefined    false

Null         false

Boolean      argument

Number       -0, 0, NaN are false, otherwise true

String       Empty string "" is false, otherwise true

Symbol       true

Object       true

ToBoolean

Boolean('true')      // true

Boolean('false')     // true

Boolean(0)           // false

Boolean(NaN)         // false

Boolean('')          // false

Boolean('0')         // true

Boolean({})          // true

Boolean(Symbol(''))  // true

Boolean(new Boolean(false)) // true

ToBoolean

!!'true'      // true

!!'false'     // true

!!0           // false

!!NaN         // false

!!''          // false

!!'0'         // true

!!{}          // true

!!Symbol('')  // true

!!new Boolean(false) // true

ToNumber

// Type        Result

Undefined    NaN

Null         0

Boolean      true is 1, false is 0

Number       No coercion

String       ...Complicated, see next slide

Symbol       TypeError

Object       ToNumber on the result of ToPrimitive

ToNumber

const bar = {
  valueOf: () => true,
  toString: () => '42'
}

Number(bar) // ??

String(bar) // ??
const bar = {
  valueOf: () => true,
  toString: () => '42'
}

Number(bar) // 1

String(bar) // "42"

bar + 1     // ??

bar + '1'   // ??

ToNumber on String

// Type        Result

Undefined    NaN

Null         0

Boolean      true is 1, false is 0

Number       No coercion

String       The number if string can be parsed as litteral number, NaN otherwise

Symbol       TypeError

Object       ToNumber on the result of ToPrimitive

ToNumber on String

Number('')          // 0

Number('    ')      // 0

Number('    \
')                  // 0

Number('1')         // 1

Number('1a')        // NaN

Number('1e')        // NaN

Number('1e1')       // 10

Number('Infinity')  // Infinity

Number('-0')        // -0

ToNumber on String

+''          // 0

+'    '      // 0

+'    \
'                  // 0

+'1'         // 1

+'1a'        // NaN

+'1e'        // NaN

+'1e1'       // 10

+'Infinity'  // Infinity

+'-0'        // -0

ToInt32

// let x be a value of any type
// Operation    Result

x | 0           integer on 32 bits, so you cannot rely on
                coercion with anything above 2**31,
                and since NaN and Infinity are not "32-bit safe",
                the result for them will be 0 

// Operation    Result

x = Infinity
x | 0           0

x = 0
1/x | 0         0

x = NaN
x | 0           0

x = 'foo'
x | 0           0

x = '42'
x | 0           ?

x = {}
x | 0           ?

ToInt32(argument) converts argument to one of 2^32 integer values in the range [-2^31, (2^31)-1]

// Operation    Result

true | 0        1

false | 0       0

2**30 | 0       1073741824

2**31 | 0       -2147483648

2**32 | 0       0

4**31 | 0       ?

5**31 | 0       -2128609280

6**31 | 0       -2147483648

8**31 | 0       ?
// let x be a value of any type
// Operation    Result

x | 0           integer on 32 bits, so you cannot rely on
                coercion with anything above 2**31,
                and since NaN and Infinity are not "32-bit safe",
                the result for them will be 0 

// Operation    Result

x = Infinity
x | 0           0

x = 0
1/x | 0         0

x = NaN
x | 0           0

x = 'foo'
x | 0           0

x = '42'
x | 0           42

x = {}
x | 0           0

ToString

// Type        Result

Undefined    "undefined"

Null         "null"

Boolean      true is "true", false is "false"

Number       NumberToString(argument)

String       argument

Symbol       TypeError

Object       [[ToString]] on the result of [[ToPrimitive]]
             (on the result of [[DefaultValue]])

ToString on a Number

// Value        Result

NaN           "NaN"

Infinity      "Infinity"

-Infinity     "-Infinity"

0 ou -0       "0" 

2e20          "200000000000000000000" 

2e21          "2e21" 

2e-6          "0.000002" 

2e-7          "2e-7" 

Totally implicit coercion

if (<coercion here>) {
}

x == y     // Coercion on one side following a set of rules

x + y      // Coercion on at least one operand following a set of rules

// Other arithmetic operators

x - y
x / y
x * y

if case

  • ToBoolean on the result of the expression

== and === case

  • == and === Type check in every case
  • If both operands are of same types, same result than === :
    • Si les 2 opérandes sont de type Number :

      - Si au moins l'un des deux opérandes est NaN, false

      - Si les 2 opérandes ont la même valeur, true,

      - Si les 2 opérandes ont pour valeurs 0 ou -0, true

      - Sinon, false

    • Sinon, retourner le résultat de SameValueNonNumber(x, y)

== and === case

SameValueNonNumber(x, y)

  • Si undefined, true
  • Si null, true
  • Si string, si x et y sont exactement la même séquence d'unités de code, true, sinon, false
  • Si boolean, si tous les 2 true ou tous les 2 false, true, sinon, false
  • Si symbol, si x et y sont le même symbol, true, sinon false
  • Si x et y sont la même valeur, true, sinon false

== case

 

  • Si l'un des opérandes est une String et l'autre un Number, l'opérande string est converti en Number avec l'opération `ToNumber` et le résultat de x == ToNumber(y) est retourné
  • Si l'un des opérandes est un Boolean, l'opérande boolean est converti en Number avec l'opération ToNumber et le résultat de x == ToNumber(y) est retourné
  • Si l'un des opérandes est un Object l'opérande object est converti en primitive avec l'opération ToPrimitive et le résultat de x == ToPrimitive(y) est retourné
[] + {} // ???

[] == ![] // ???

{} + [] // ???

({}) + [] // ???

[] + [] // ???

[] + +[] // ???

[] ++[] // ???

{} + {} // ???

{foo: 'bar'} + [] // ???

({foo: 'bar'}) + [] // ???

1 - - + + + - + -41 // ???

1 - - + + + - + -'41' // ???

1 + '41' // ???

43 - '1' // ???

!!~'areyouthere'.indexOf('no')

Type coercion in JavaScript

By Stanislas Ormières

Type coercion in JavaScript

  • 1,176