const str = 'Hello world!'
const num = 42
const boo = true
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 // ??
[] + {} // ???
[] == ![] // ???
{} + [] // ???
({}) + [] // ???
[] + [] // ???
[] + +[] // ???
[] ++[] // ???
{} + {} // ???
{foo: 'bar'} + [] // ???
({foo: 'bar'}) + [] // ???
1 - - + + + - + -41 // ???
1 - - + + + - + -'41' // ???
1 + '41' // ???
43 - '1' // ???
!!~'areyouthere'.indexOf('no')
CTO: "En JavaScript, tout est objet"
Me: "Ben... Non, il y a aussi des primitives"
CTO: "..."
"Je ne comprends pas pourquoi
[] + [] == ""
"
"C'est expliqué dans la spécification"
"Oui, mais d'après moi, ça devrait être "
NaN
"OK... Je vais faire un talk dessus"
La conversion de type
(Type coercion)
Types
8 types en JavaScript
- Undefined
- Null
- Boolean
- String
- Number
- Symbol (depuis ES2015)
- BigInt (depuis ES2020)
- Object
An ECMAScript language type corresponds to values that are directly manipulated by an ECMAScript programmer using the ECMAScript language.
ecma-262 11.0 (ECMAScript 2020)
The ECMAScript language types are Undefined, Null, Boolean, String, Symbol, Number, BigInt, and Object.
An ECMAScript language value is a value that is characterized by an ECMAScript language type.
7 types primitifs
(7 primitive data types)
- Undefined
- Null
- Boolean
- String
- Number
- Symbol (depuis ES2015)
- BigInt (depuis ES2020)
Toute autre valeur sera de type Object
L’opérateur typeof
- "undefined"
- "boolean"
- "number"
- "string"
- "symbol"
- "bigint"
- "object"
- "function"
8 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) // ? ? ?
})()
(function (undefined) {
var foo
console.log(foo === undefined, foo, undefined) // ? ? ?
})(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) // ? ? ?
})()
Ne faites pas ça !
(function () {
undefined = 42
var foo
console.log(foo === undefined, foo, undefined) // true undefined undefined
})()
Ne faites pas ça !
(function () {
var undefined = 42
var foo
console.log(foo === undefined, foo, undefined) // ? ? ?
})()
Ne faites pas ça !
(function () {
var undefined = 42
var foo
console.log(foo === undefined, foo, undefined) // false undefined 42
})()
Ne faites pas ça !
(function (undefined) {
var foo
console.log(foo === undefined, foo, undefined) // ? ? ?
})(42)
Ne faites pas ça !
(function (undefined) {
var foo
console.log(foo === undefined, foo, undefined) // false undefined 42
})(42)
Ne faites pas ça !
(function () {
'use strict'
undefined = 42
var foo
console.log(foo === undefined, foo, undefined)
})()
Ne faites pas ça !
(function () {
'use strict'
undefined = 42 // Type Error
var foo
console.log(foo === undefined, foo, undefined)
})()
Ne faites pas ça !
(function () {
'use strict'
var undefined = 42
var foo
console.log(foo === undefined, foo, undefined) // ? ? ?
})()
Ne faites pas ça !
(function () {
'use strict'
var undefined = 42
var foo
console.log(foo === undefined, foo, undefined) // false undefined 42
})()
Ne faites pas ça !
(function (w, d, undefined) {
// Ici undefined a pour valeur undefined quoi qu'il arrive
})(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)
Les cas de Number
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 // Il y a 13 `9`
isCloseEnough(0.4 + 0.0199999999999999, 0.42) // true // Il y a 14 `9`
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!'.toUpperCase(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 (Boolean(x) === true) {
}
if (42) {
}
if ('') {
}
if ('0') {
}
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, 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 :
-
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)
-
Strict equality
Abstract equality
==
===
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
(2 opérandes de même type)
Le cas ==
-
Null == Undefined ou Undefined == Null
true -
String == Number
ToNumber(String) == Number -
Boolean == *
ToNumber(Boolean) == * -
Object == Primitive
ToPrimitive(Object) == Primitive
Abstract Equality Comparison
(2 opérandes de type différents)
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 +
- GetValue
- ToPrimitive
- Si la résultante d'une des deux 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
Conversion de types en JavaScript
By Stanislas Ormières
Conversion de types en JavaScript
- 626