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  // ??

Type coercion

Types

7 types en JavaScript

  • Undefined
  • Null
  • Boolean
  • String
  • Number
  • Symbol (depuis ES2015)
  • Object

(Bientôt 8 avec BigInt dans ES2020)

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 (depuis ES2015)

Tout autre valeur sera de type Object

(Bientôt 7 types primitifs avec BigInt)

typeof operator

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

7 retours possibles... mais pas (tout à fait) les mêmes

Le cas null

  • C'est une valeur
  • C'est un type...
  • mais
  • Il s'agit d'un bug qui ne sera jamais corrigé
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)

Ne faites pas ça !

Le cas de undefined

Le cas de undefined

  • C'est une valeur
  • C'est un type
  • Et aussi un identifiant valide (ce n'est pas un mot réservé)
(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)
})()

Ne faites pas ça !

(function (w, d, undefined) {
  // Code where undefined is the undefined value no matter what
})(window, document)
(function (w, d, undefined) {
  // Ici undefined a pour valeur undefined quoi qu'il arrive
})(window, document)



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

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          

Les cas de Number

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
  • Ce n'est PAS propre à JavaScript
  • Ce qui est en cause ici c'est la représentation des nombres à virgules flottantes en 64 bits
  • Cf. Spécification IEEE 754 ou wikipedia
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 et retour à la 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" 

Conversion totalement implicite

if (<conversion ici>) {
}

x == y     // Conversion éventuelle si les 2 types des opérandes sont différents,
           // selon un ensemble de règles

x + y      // Conversion éventuelle selon un ensemble de règles

// Autres opérateurs arithmétiques

x - y
x / y
x * y
  • ToBoolean sur le résultat de l'expression

Le cas de if

Les cas == et ===

  • Pour == et === il y a toujours une vérification des types
  • Si les 2 opérandes sont de même type, même algo que === :
    • 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)

Les cas == et ===

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

Le cas ==

 

  • Null == Undefined ou Undefined == Null
    true
  • String == Number
    ToNumber(String) == Number
  • Boolean == *
    ToNumber(Boolean) == *
  • Object == Primitive
    ToPrimitive(Object) == Primitive

Abstract Equality Comparison

Le cas ==

 

Null == Undefined ou Undefined == Null
true

Abstract Equality Comparison

if (variable === null || variable === undefined) { // Wrong
  (...)
}

if (variable == null) { // Right
  (...)
}
if (variable === null || variable === undefined) { // Verbose
  (...)
}

if (variable == null) { // Expressive
  (...)
}


if (typeof variable === 'function') { // Useless
  (...)
}

if (typeof variable == 'function') { // Better (I think)
}
if (variable === null || variable === undefined) { // Verbose
  (...)
}

if (variable == null) { // Expressive
  (...)
}


if (typeof variable === 'function') { // Useless
  (...)
}

if (typeof variable == 'function') { // Better (I think)
}

// .eslintrc.js
{
  rules: {
    eqeqeq: ["error", "smart"],
  }
}

Le cas +

 

  1. GetValue
  2. ToPrimitive
  3. Si l'une des deux résultantes des opérandes est de type String, alors, concaténation, sinon, addition
[] + {} // ???

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

{} + [] // ???

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

[] + [] // ???

[] + +[] // ???

[] ++[] // ???

{} + {} // ???

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

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

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

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

1 + '41' // ???

43 - '1' // ???

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

Spread the Love!

(of JavaScript)

[] + {} // ???

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

{} + [] // ???

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

[] + [] // ???

[] + +[] // ???

[] ++[] // ???

{} + {} // ???

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

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

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

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

1 + '41' // ???

43 - '1' // ???

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

?

[] + {}

GetValue([]) + GetValue({})

[] + {}

ToPrimitive([]) + ToPrimitive({})

'' + '[object Object]'



// Résultat final
'[object Object]'

?

[] == ![]

[] == !(ToBoolean(ToPrimitive([]))

[] == !(ToBoolean(''))

[] == !true

[] == false

// Les types sont différents, donc conversion de type
ToPrimitive(GetValue([])) == ToNumber(false)

ToPrimitive([]) == 0

'' == 0

ToNumber('') == 0

0 == 0


// Résultat final
true

?

{} + []

+ [] // {} en début de ligne est un bloc de code... vide

ToNumber(ToPrimitive(GetValue([])))

ToNumber('')


// Résultat final
0