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