aller au contenu principal

Today I Learned - Février 2023

Ce que j'ai appris en février 2023


Publié le 01/02/2023 dans TIL
Jour #3201/02/2023

Aujourd'hui j'ai compris la différence entre une déclaration et l'assignation d'une variable. Pour faire simple :

  • déclarer une variable c'est lui donner un identifiant, un nom
  • alors qu'assigner une variable c'est lui donner une valeur
// Déclaration ⬇️
let name
// Assignation ⬇️
name = "Seb"
// Assignation au moment de la déclaration ⬇️
let name = "Seb"

Pour les const il faut obligatoirement leur assigner une valeur au moment de la déclaration. La déclaration seule n'est possible qu'avec var et let.

Jour #3302/02/2023

Pour comprendre la portée des variables en JavaScript, il faut comprendre les différences entre les scopes.

Il y a deux types de scopes : le global scope, et le local scope.

Au sein du local scope on peut distinguer deux autres scopes : le function scope et le block scope.

// Je suis dans le "global scope" ! 😸
function x() { 
  // Là je suis dans le "local scope" 😽
  // Mais je suis aussi dans un "function scope" ! 🙀
  if (true) {
    // Cette fois je suis dans un "block scope" 😺
  }
}
// Me voila revenu dans le "global scope" ! 😸
Jour #3403/02/2023

Lorsqu'on déclare une variable, le mot-clé utilisé et le scope dans lequel elle se trouve vont déterminer son accessibilité (on appelle aussi cela la portée des variables). Aujourd'hui il est conseillé de déclarer des variables avec let ou const et d'abandonner l'utilisation du mot-clé var.

Lorsqu'elle sont déclarées dans un block scope, let et const ne seront accessibles que dans ce scope, jamais en dehors.

function x() {
  if (true) {
    let y = 1
    const z = 1
  }
  console.log(y) // Uncaught ReferenceError: y is not defined
  console.log(z) // Uncaught ReferenceError: y is not defined
}

Alors qu'une variable déclarée avec var dans un block scope (entre les accolades d'un if par exemple), sera aussi accessible en dehors de ce scope, cependant elle ne sera pas accessible en dehors d'un function scope.

function x() {
  if (true) {
    var y = 1
  }
  console.log(y) // undefined
}

Dans un block, l'accès aux variables définies dans les blocks extérieurs est possible, c'est le lexical scoping :

function x() {
  const y = 1
  if (true) {
    console.log(y) // 1
    if (true) {
      console.log(y) // 1
    }
  }
}
Jour #3504/02/2023

Les variables définies avec let peuvent être mises à jour mais pas redéclarées, ce qui est possible avec var :

function x() {
  var y = 1
  var y = 2
  console.log(y) // 2
}
function x() {
  let y = 1
  let y = 2
  console.log(y)
  // Uncaught SyntaxError: Identifier 'y' has already been declared
}

Une variable créée avec let ou const peut avoir le meme nom tant qu'elle ne partage pas le même scope.

function x() {
  let y = 1
  if (true) {
    let y = 2
    console.log(y) // 2
  }
  console.log(y) // 1
}
Jour #3605/02/2023

Il y a toujours du hoisting (remontée des variables) avec let et const, mais à la différence de var, elles ne sont pas initialisées avec la valeur undefined.

// Le hoisting fait que la variable x remonte tout en haut du scope (ici global)
// C'est comme s'il y avait un : var x
console.log(x) // undefined
var x = 1
console.log(x) // 1

Les variables déclarées avec let et const sont hissées (hoisted) mais sans être initialisées :

console.log(x) // Uncaught ReferenceError: Cannot access 'x' before initialization
let x = 1
console.log(x) // 1
Jour #3706/02/2023

JavaScript est capable de faire des conversions de type de données de façon implicite/automatique, on appelle cela la type coercion (ou coercition de type en français). Par exemple 2 + "2" renverra "22" car le JavaScript transforme ici les nombres en chaîne de caractères.

JavaScript fera toujours la conversion la plus simple. Transformer un nombre en chaîne de caractères est simple, mais l'inverse l'est beaucoup moins : la chaîne de caractères "100" peut devenir 100 facilement, ce qui n'est pas le cas de "hello"..

Dans l'addition d'un nombre et d'une chaîne de caractères, JavaScript convertira le nombre, car c'est la solution la plus simple :

console.log(2 + "2") // "22"

Un autre exemple de conversion implicite : celui des booléens. Encore une fois c'est la conversion la plus simple qui est faîte. Dans le cas d'une addition d'un nombre avec un booléen, true sera convertit en nombre et vaudra 1, false quant à lui vaudra 0.

console.log(1 + true) // 2
console.log(true + "hello") // "truehello"
Jour #3807/02/2023

Les opérations mathématiques sont exécutées de gauche à droite, c'est pour cela que 2 + 2 + "2" renverra "42".

D'abord, 2 + 2 sera résolu pour donner 4. Puis voyant qu'on essaye d'additionner un nombre avec une chaîne de caractères, JavaScript convertira le nombre 4 en chaîne de caractères (il fait la conversion la plus simple).

Ce qui donnera la concaténation "4" + "2".

console.log(2 + 2 + "2") // "42"
Jour #3908/02/2023

Il est possible de faire des conversions de type de façon manuelle :

Boolean("true") // true
Number("1") // 1
String(42) // "42"
Number(true) // 1
Boolean(42) // true
Boolean("") // false

Si on essaye de convertir la chaîne de caractères "hello" en nombre, cela renverra NaN (Not a Number) :

Number('hello') // NaN

Si on fait un typeof de NaN, cela retourne "number". Car NaN est un nombre qui ne peut pas être défini dans les limites du type "number".

Jour #4009/02/2023

Les variables déclarées avec var sont globales si elles sont déclarées en dehors d'un scope de fonction. Elles deviennent alors accessibles en tant que propriétés de l'objet window, ce n'est pas cas de const et let qui peuvent être déclarées de façon globale mais ne seront pas des propriétés de l'objet window.

var x = 1
console.log(window.x) // 1

Une variable déclarée avec var mais dans un scope de fonction :

function x() {
  var y = 1
}
x()
console.log(y) // undefined
console.log(window.y) // undefined

Une variable déclarée avec var mais dans un scope de block :

if (true) {
  var x = 1
}
console.log(x) // 1
console.log(window.x) // 1

Enfin, un exemple avec let :

let x = 1
console.log(x) // 1
console.log(window.x) // undefined
Jour #4110/02/2023

Aujourd'hui j'ai appris qu'il existait les dense arrays et les sparse arrays en JavaScript.

Un dense array est un tableau "normal" dont les éléments se suivent de façon séquentielle, et qui commence à l'index 0 :

const fruits = ["pomme", "banane", "orange"]
fruits[0] // "pomme"

Un sparse array est un tableau dont les éléments ne se suivent pas de façon séquentielle, et qui ne commence pas toujours par 0. Il existe plusieurs façons de créer un sparse array :

const x = [1, , 3]
const y = Array(5)

const z = []
z[100] = 'a'
console.log(z) // [vide × 100, 'a']
console.log(z.length) // 101
console.log(z[50]) // undefined

Les "slots" vide sont non défini (undefined). On comprend donc que la length d'un tableau correspond à la valeur de l'index le plus grand + 1, et non au nombre d'éléments à l'intérieur.

Jour #4211/02/2023

Aujourd'hui j'ai appris à faire la différence entre les rest parameters et l'opérateur spread.

Les rest paramaters permettent de représenter un nombre indéfini d'arguments sous forme de tableau :

function sum(...numbers) {
  console.log(numbers)
}
sum(1, 2, 3) // [1, 2, 3]

Il viennent remplacer en quelque sorte l'utilisation de l'objet arguments, qui est semblable à un tableau, qui correspond aux arguments passés à une fonction mais sur lequel on ne peut pas utiliser des méthodes comme pop() ou forEach()..

Alors que le spread, qui a la même syntaxe, permet de décomposer un itérable comme les tableaux ou les chaînes de caractères. C'est l'exact opposé du rest. On peut s'en servir dans les appels de fonction ou pour concaténer plus facilement des tableaux :

const numbers = [1, 2, 3]
sum(...numbers) // équivalent à sum(1, 2, 3)

const x = [1, 2]
const y = [3, 4]
const arr = [...x, ...y] // [1, 2, 3, 4]
Jour #4312/02/2023

On peut se servir des rest parameters dans de l'array destructuring :

const letters = ['a', 'b', 'c']
const [a, ...others] = letters
console.log(others) // ['b', 'c']
Jour #4413/02/2023

Il est possible de faire du destructuring sur des chaînes de caractères :

const x = 'hello'
const [h, ...otherLetters] = x
console.log(h) // "h"
console.log(otherLetters) // ["e", "l", "l", "o"]
Jour #4514/02/2023

Lorsqu'on fait un "default export" en JavaScript, il est possible lors de l'import de lui donner le nom que l'on souhaite.

Par exemple, un fichier Button.js avec un export default function Button() {} pourrait être importé de cette manière : import Banane from './Button'.

Même si on exporte une constante comme dans l'exemple ci-dessous, cela reste un "default export" et non un "named export" si on utilise le mot-clé default :

const Button = () =>  {}
export default Button

L'import pourra être fait de ces façons :

import Button from './Button'
// Ou
import Banane from './Button'
Jour #4615/02/2023

Un "named export" est un export qui doit avoir le même nom des deux côtés : côté import et export. Exemple de "named export" :

export function Button() {}

L'import devra être fait de cette façon :

import { Button } from './Button'
Jour #4716/02/2023

Il est possible de faire plusieurs exports dans un fichier JavaScript mais il ne peut y avoir qu'un seul "default export".

export default function Button() {}
export function Search() {}
export function Profile() {}

Les imports pourront être fait comme ceci :

import Button, { Search, Profile } from '...'
Jour #4817/02/2023

Il est possible de donner des noms différents aux "named exports" en leur donnant un alias, cela permet de mieux les identifier selon le contexte.

export function Header() {}
// SpecialHeader.js
import {Header as SpecialHeader} from './Header'

Autre raison de vouloir utiliser des alias : des "named exports" peuvent partager le même nom, dans ce cas un alias permet de les distinguer.

// Header.js
export function Header() {}

// HeaderSecondary.js
export function Header() {}
import {Header} from './Header'
import {Header as HeaderSecondary} from './HeaderSecondary'

Les "default exports" sont déjà des alias, puisqu'on est libre de leur donner le nom que l'on souhaite.

Jour #4918/02/2023

L'opérateur de reste % permet d'obtenir le reste d'une division.

10 % 2 // 0
10 % 3 // 1

J'ai eu besoin de m'en servir pour créer une fonction permettant de savoir si un nombre est divisible :

function isDivisible(n, x) {
  return n % x === 0
}
isDivisible(10, 2) // true
isDivisible(10, 3) // false
Jour #5019/02/2023

Aujourd'hui j'ai compris la différence entre Number('123') et new Number('123').

Number() va convertir le paramètre qu'on lui passe en valeur de type primitif numérique.

Tandis que new Number() va créer une enveloppe objet (wrapper) :

const x = Number('123')
typeof x // 'number'
x instanceof Number // false

const y = new Number('123')
typeof y // 'object'
y instanceof Number // true

new Number() permet donc de créer une instance de Number, il est alors possible de lui rattacher des propriétés.

const x = new Number('123')
x.message = "Hello"
console.log(x.message) // "Hello"

const y = Number('123')
y.message = "Hello"
console.log(y.message) // undefined
Jour #5120/02/2023

Aujourd'hui je me suis rendu compte que je ne connaissais pas la différence entre une fonction et une méthode.

Contrairement à une fonction, une méthode est une propriété d'un objet qui a pour valeur une fonction.

const obj = {
  sayHello: function() {
    return "Hello"
  }
}
obj.sayHello() // "Hello"

Une méthode reçoit implicitement l'objet sur lequel elle a été appelée, et elle peut opérer sur des données contenues dans l'objet.

Autre chose qui sépare une méthode d'une fonction : le receiver.

Dans l'exemple précédent, l'objet obj est le receiver, une fonction n'a pas de receiver.

Cependant lorsqu'une fonction est déclarée dans le global scope, elle devient une propriété de l'objet window (dans le contexte du navigateur), ce qui en fait une méthode ?

function sayHello() {
  return "Hello"
}
window.sayHello() // "Hello"
Jour #5221/02/2023

Aujourd'hui j'ai appris la différence entre les paramètres et les arguments.

Un paramètre est une variable d'une fonction. Et lorsqu'une fonction est appelée, les arguments sont les données que l'on transmet aux paramètres de la fonction.

function add(x, y) {
  // x et y sont les paramètres de la fonction add()
  return x + y
}
add(6, 4)
// 6 et 4 sont les arguments transmis aux paramètres de la fonction add()

Les paramètres sont en quelque sorte des placeholders pour les arguments.

Jour #5322/02/2023

Aujourd'hui j'ai compris la différence entre une déclaration de fonction et une expression de fonction.

Une déclaration de fonction commence par le mot-clé function, alors qu'une expression de fonction est une fonction stockée dans une variable :

// Déclaration de fonction
function x() {
  return 1
}

// Expression de fonction
const y = function() {
  return 1
}

Il y a du hoisting sur les déclarations de fonction, ce n'est pas le cas sur les expressions de fonction :

console.log(x()) // 1
function x() { return 1 }

console.log(y()) // Uncaught ReferenceError: Cannot access 'y' before initialization
const y = function() { return 1 }
Jour #5423/02/2023

Les types de données primitives en JavaScript sont :

  • string
  • number
  • bigint
  • boolean
  • undefined
  • null
  • et symbol

La caractéristique principale de ces types est que leurs valeurs sont "immutables" (immuables en français), ce qui signifie qu'elles ne peuvent pas changer.

let x = "Hello"
let y = x
x = "Bonjour"

console.log(x) // Bonjour
console.log(y) // Hello

Les valeurs primitives sont stockées dans un espace appelé la mémoire "statique", aussi appelée "stack".

Lorsqu'on modifie une primitive, on ne change pas sa valeur, mais on crée une nouvelle "adresse" dans la mémoire statique.

let x = "Hello" // Une adresse en mémoire statique est créée et contient "Hello"
x = "Bonjour" // Une nouvelle adresse est créée et contient désormais "Bonjour"

// La première adresse créée sera libérée dès que possible, car plus utilisée
Jour #5524/02/2023

La valeur des objets est stockée par référence en JavaScript, les types suivants sont considérés comme des objets :

  • objets
  • fonctions
  • collections
  • tableaux
  • dates
  • les autres type d'objets

Contrairement aux primitives, les valeurs des objets sont dîtes "complexe", elles ne peuvent pas être stockées dans la mémoire "statique".

Elles sont stockées dans la partie "dynamique", aussi appelée "heap", de la mémoire car leur taille est variable et peut évoluer au fil de l'exécution du script.

D'ailleurs on ne stocke pas la valeur de la variable, mais la référence vers cette variable. Donc si plusieurs variables contiennent la même référence, la modification de cette dernière sera effective pour toutes les variables qui pointent dessus :

const x = { a: 1 }
const y = x
x.a = 2

console.log(x) // { a: 2 }
console.log(y) // { a: 2 }
Jour #5625/02/2023

Les nombres, booléens, et les chaînes de caractères sont des primitives, qui contrairement aux objets n'ont pas de propriétés et de méthodes.

Pourtant il est possible d'utiliser des méthodes comme substring() ou la propriété length sur les chaînes de caractères.

La raison est simple : lorsque qu'on utilise une méthode ou une propriété sur une chaîne de caractères, JavaScript va utiliser le constructeur new String() et envelopper la primitive pour créer un object wrapper, les méthodes et propriétés sont alors accessibles sur l'objet créé, la valeur retournée par la méthode ou la propriété est renvoyée sous forme de primitive.

Jour #5726/02/2023

En programmation, une fonction variadique est une fonction qui peut prendre un nombre variable de paramètres (la limite est de 255 en JavaScript).

Par exemple Math.min() et Math.max() sont des fonctions variadiques.

Le spread operator et le rest operator ont facilités l'utilisation des fonctions variadiques.

function f(a, b, ...args) {
  // ...
}
Jour #5827/02/2023

Une fonction retourne toujours quelque chose, même s'il n'y a pas le mot-clé return.

Une fonction qui ne retourne rien, de façon explicite ou implcite, retournera undefined. C'est de cette façon qu'elle sort du contexte d'exécution actuel et passe à l'étape suivante du code.

Dans cet exemple, la fonction ne retourne rien de façon explicite :

function makeDrink(name, price) {
  const drink = {
    name,
    price
  }
}
makeDrink('Limonade', 5)
// undefined

Le même exemple mais avec un return explicite :

function makeDrink(name, price) {
  return {
    name,
    price
  }
}
makeDrink('Limonade', 5)
// { name: 'Lemonade', price: 5 }
Jour #5928/02/2023

Il y a deux façons d'écrire les arrows functions : une version "longue" et une version "courte".

La version "courte" retourne toujours quelque chose :

const getDrink = () => "Limonade"

La version "longue" avec les accolades permet de faire des return explicites et implicites :

const getDrink = (name, price) => {
  return 
    name,
    price
  }
}

Pour éviter d'avoir trop d'accolades (celles de l'objet et celles de la fonction), il est possible de faire un return implicite.

Cependant cette syntaxe ne fonctionnera pas :

const getDrink = (name, price) => {
  name,
  price
}

L'astuce ici est d'entrouré l'objet retourné avec des paranthèses :

const getDrink = (name, price) => ({
  name,
  price
})
2024 Sébastien Imbert