Skip to content

6. Les chaînes de caractères

Les scripts de ce chapitre se trouvent dans le dossier [strings] du projet.

6.1. script [str-01]

La première chose à comprendre est qu'une fois une chaîne créée, elle n'est plus modifiable (elle est immuable). On dispose de nombreuses méthodes pour produire une nouvelle chaîne à partir de la chaîne initiale, mais celle-ci reste toujours inchangée.

'use strict';

// les chaînes de caractères sont en lecture seule (on ne peut pas les modifier)

// une chaîne
const chaîne1: string = "abcd ";
// type
console.log("typeof(chaîne1)=", typeof (chaîne1));
// caractère n° 2
console.log("chaîne1[2]=", chaîne1[2]);
// provoque une erreur
// [mise à jour TypeScript] TypeScript détecte l'erreur dès la compilation :
// une chaîne n'a qu'un accès en lecture par index
// @ts-expect-error : une chaîne de caractères est en lecture seule
chaîne1[2] = "0";
  • ligne 10 : chaîne1[2] accède en lecture au 3ᵉ caractère de la chaîne (index 0) ;
  • ligne 15 : chaîne1[2] = "0" tente une écriture, ce que TypeScript refuse dès la compilation, grâce à @ts-expect-error — le commentaire au-dessus confirme que cette erreur est attendue, et sert justement à la démontrer.

Vérification de types :

npx tsc --noEmit

Résultat de l'exécution :

strings/str-01.ts(15,1): error TS2540: Cannot assign to '2' because it is a read-only property.

Exécution avec tsx (sans vérification de types préalable) :

npx tsx strings/str-01.ts

Résultat de l'exécution :

1
2
3
4
typeof(chaîne1)= string
chaîne1[2]= c
TypeError: Cannot assign to read only property '2' of string 'abcd '
    at strings/str-01.ts:15:10

6.2. script [str-02]

Une chaîne de caractères peut être de deux types : [string] (chaîne littérale) ou [object] (instance de la classe [String]) :

'use strict';

// les chaînes de caractères peuvent être de deux types

// une chaîne littérale
const chaîne1: string = "abcd ";
// type
console.log("typeof(chaîne1)=", typeof (chaîne1));
// instance de String
// eslint-disable-next-line @typescript-eslint/ban-types -- ici [String] (objet) est le type exact voulu, pas [string] (primitif) : c'est justement le sujet de la démo
const chaîne2: String = new String("xyzt");
// type
console.log("typeof(chaîne2)=", typeof (chaîne2));
// autre écriture (sans new)
const chaîne3: string = String("12 34");
// type
console.log("typeof(chaîne3)=", typeof (chaîne3));
// le type [string] et le type [object] offrent les mêmes méthodes, celles de la classe String
console.log("chaîne1.length=", chaîne1.length);
console.log("chaîne2.length=", chaîne2.length);
  • ligne 6 : la façon usuelle de définir une chaîne — chaîne1 sera de type string ;
  • ligne 11 : new String(...) construit une chaîne objetchaîne2 sera de type object. Ici, le type TypeScript déclaré est String (avec majuscule), le type *objet enveloppe*, différent du type primitif string (minuscule) ;
  • les deux types offrent les mêmes méthodes, celles de la classe String.
npx tsx strings/str-02.ts

Résultat de l'exécution :

1
2
3
4
5
typeof(chaîne1)= string
typeof(chaîne2)= object
typeof(chaîne3)= string
chaîne1.length= 5
chaîne2.length= 4

6.3. script [str-03]

Ce script montre l'interpolation de variables dans une chaîne :

1
2
3
4
5
6
7
'use strict';

// chaine
const chaîne: string = "Introduction à Javascript par l'exemple";
// chaîne avec interpolation de variables
const str: string = `[${chaîne}].substr(3, 2)=` + chaîne.substr(3, 2)
console.log(str);
  • ligne 6 : une chaîne entourée d'apostrophes inverses (*backticks*, AltGr-7 sur un clavier français) peut contenir des expressions ${variable}, remplacées par la valeur de la variable — on parle de chaîne gabarit (*template string*).
npx tsx strings/str-03.ts

Résultat de l'exécution :

[Introduction à Javascript par l'exemple].substr(3, 2)=ro

6.4. script [str-04]

La chaîne gabarit reste insuffisante pour un formatage précis (largeur, alignement, précision décimale...). On utilise pour cela un package externe, [sprintf-js], qui reproduit les fonctions printf/sprintf bien connues d'autres langages. Il s'installe avec npm install sprintf-js, et s'importe simplement :

1
2
3
4
5
6
7
'use strict';
// utilisation d'un package externe pour disposer de la fonction sprintf
import { sprintf } from 'sprintf-js';
// chaine
const chaîne: string = "Introduction à Javascript par l'exemple";
// méthode
console.log(sprintf("[%s].substr(3,2)=[%s]", chaîne, chaîne.substr(3, 2)));
Historique : en 2019, l'instruction import posait problème avec Node.js (qui ne comprenait pas encore nativement les modules ECMAScript), et nécessitait un package intermédiaire appelé esm. Ce n'est plus le cas depuis Node 12+ : notre projet, avec "type": "module" dans package.json, utilise import/export nativement, sans aucune configuration supplémentaire.
npx tsx strings/str-04.ts

Résultat de l'exécution :

[Introduction à Javascript par l'exemple].substr(3,2)=[ro]

6.5. script [str-05]

La fonction [sprintf] accepte, comme en C, des « formats » introduits par % : %s (chaîne), %d (entier), %f (réel), %j (JSON), %T (type), %t (booléen), avec des modificateurs de largeur et d'alignement (%20s, %-20s, %04d...) :

'use strict';
// utilisation d'un package externe pour disposer de la fonction sprintf
import { sprintf } from 'sprintf-js';
// chaine
const chaîne: string = "Javascript";
// chaînes de caractères
console.log(sprintf("[%s, %%s]=>[%s]", chaîne, chaîne));
console.log(sprintf("[%s, %%20s]=>[%20s]", chaîne, chaîne));
console.log(sprintf("[%s, %%-20s]=>[%-20s]", chaîne, chaîne));
// entiers
console.log(sprintf("[%d, %%d]=>[%d]", 10, 10));
console.log(sprintf("[%d, %%4d]=>[%4d]", 10, 10));
console.log(sprintf("[%d, %%-4d]=>[%-4d]", 10, 10));
console.log(sprintf("[%d, %%04d]=>[%04d]", 10, 10));
// réels
console.log(sprintf("[%f, %%f]=>[%f]", -10.5, -10.5));
console.log(sprintf("[%f, %%10.2f]=>[%10.2f]", -10.5, -10.5));
console.log(sprintf("[%f, %%-10.2f]=>[%-10.2f]", -10.5, -10.5));
console.log(sprintf("[%f, %%010.3f]=>[%010.3f]", -10.5, -10.5));
// json
console.log(sprintf("personne (%%j)=%j", { nom: "mathieu", âge: 34 }));
// type
console.log(sprintf("type personne (%%T)=%T", { nom: "mathieu", âge: 34 }));
// booléen
console.log(sprintf("booléen (%%t)=%t", 4 === 4));
npx tsx strings/str-05.ts

Résultat de l'exécution :

[Javascript, %s]=>[Javascript]
[Javascript, %20s]=>[          Javascript]
[Javascript, %-20s]=>[Javascript          ]
[10, %d]=>[10]
[10, %4d]=>[  10]
[10, %-4d]=>[10  ]
[10, %04d]=>[0010]
[-10.5, %f]=>[-10.5]
[-10.5, %10.2f]=>[    -10.50]
[-10.5, %-10.2f]=>[-10.50    ]
[-10.5, %010.3f]=>[-00010.500]
personne (%j)={"nom":"mathieu","âge":34}
type personne (%T)=object
booléen (%t)=true

6.6. script [str-06]

Ce script présente les méthodes les plus courantes de la classe [String] :

'use strict';
// utilisation d'un package externe pour disposer de la fonction sprintf
import { sprintf } from 'sprintf-js';
// chaine
const chaîne: string = "  Introduction à Javascript ";
// quelques méthodes
// substr(10,2) : 2 caractères à partir du n° 10
console.log(sprintf("[%s].substr(10,2)=[%s]", chaîne, chaîne.substr(10, 2)));
// trim : élimination des blancs de début et fin de chaîne (blanc=\b \t \r \n \f)
console.log(sprintf("[%s].trim()=[%s]", chaîne, chaîne.trim()));
// toLowerCase : transformation en minuscules
console.log(sprintf("[%s].toLowerCase=[%s]", chaîne, chaîne.toLowerCase()));
// toUpperCase : transformation en majuscules
console.log(sprintf("[%s].toUpperCase=[%s]", chaîne, chaîne.toUpperCase()));
// indexOf : position d'une chaîne  cherchée dans la chaîne, -1 si la sous-chaîne n'existe pas
console.log(sprintf("[%s].indexOf('Java')=[%s]", chaîne, chaîne.indexOf('Java')));
console.log(sprintf("[%s].trim().indexOf('abcd')=[%s]", chaîne, chaîne.indexOf('abcd')));
// includes : vrai si la chaîne cherchée est dans la chaîne
console.log(sprintf("[%s].includes('Java')=[%s]", chaîne, chaîne.includes('Java')));
// length : longueur de la chaîne - n'est pas une méthode mais une propriété
console.log(sprintf("[%s].length=[%s]", chaîne, chaîne.length));
// slice (7,10) : chaînes des caractères n° 7 à 9
console.log(sprintf("[%s].slice(7,10)=[%s]", chaîne, chaîne.slice(7, 10)));
// match : cherche une expression dans la chaîne - cette expression peut être une expression régulière
// /intro/i : expression régulière désignant la chaîne [intro] en majuscules ou minuscules
// rend la chaîne trouvée
console.log(sprintf("[%s].match(/intro/i)=[%s]", chaîne, chaîne.match(/intro/i)));
// replace : remplace chaine1 par chaine2 dans chaîne
// remplace la 1ère occurrence de i par x
console.log(sprintf("[%s].replace('i','x')=[%s]", chaîne, chaîne.replace('i', 'x')));
// remplace toutes les occurrences de i par x
// /i/g est une expression régulière désignant toutes (g) les occurrences de i
console.log(sprintf("[%s].replace(/i/g,'x')=[%s]", chaîne, chaîne.replace(/i/g, 'x')));
// split : divise la chaîne en mots séparés par le paramètre de split
// rend le tableau de ces mots
// /\s*/ : mots séparés par 0 ou plusieurs espaces
console.log(sprintf("[%s].split(/\\s*/)=[%s]", chaîne, chaîne.split(/\s*/)));
// /\s+/ : mots séparés par un ou plusieurs espaces
console.log(sprintf("[%s].split(/\\s+/)=[%s]", chaîne, chaîne.split(/\s+/)));
  • [trim] élimine les blancs de début et fin ; [toLowerCase]/[toUpperCase] changent la casse ;
  • [indexOf] rend la position d'une sous-chaîne (-1 si absente) ; [includes] rend un booléen ;
  • [slice] extrait une sous-chaîne par position ; [match] confronte la chaîne à une expression régulière (voir chapitre suivant) ;
  • [replace] remplace la 1ère occurrence trouvée (ou toutes, avec une regexp /g) ; [split] découpe la chaîne en un tableau de mots.
npx tsx strings/str-06.ts

Résultat de l'exécution :

[  Introduction à Javascript ].substr(10,2)=[ti]
[  Introduction à Javascript ].trim()=[Introduction à Javascript]
[  Introduction à Javascript ].toLowerCase=[  introduction à javascript ]
[  Introduction à Javascript ].toUpperCase=[  INTRODUCTION À JAVASCRIPT ]
[  Introduction à Javascript ].indexOf('Java')=[17]
[  Introduction à Javascript ].trim().indexOf('abcd')=[-1]
[  Introduction à Javascript ].includes('Java')=[true]
[  Introduction à Javascript ].length=[28]
[  Introduction à Javascript ].slice(7,10)=[duc]
[  Introduction à Javascript ].match(/intro/i)=[Intro]
[  Introduction à Javascript ].replace('i','x')=[  Introductxon à Javascript ]
[  Introduction à Javascript ].replace(/i/g,'x')=[  Introductxon à Javascrxpt ]
[  Introduction à Javascript ].split(/\s*/)=[,I,n,t,r,o,d,u,c,t,i,o,n,à,J,a,v,a,s,c,r,i,p,t,]
[  Introduction à Javascript ].split(/\s+/)=[,Introduction,à,Javascript,]

6.7. script [str-07]

[NOUVEAU depuis 2019] Trois nouveautés apparues entre 2020 et 2024 : [replaceAll], [matchAll], et la détection/correction des chaînes « mal formées » :

'use strict';
// ========================================================================
// [NOUVEAU depuis 2019] nouvelles méthodes des chaînes de caractères
// ========================================================================

// ------------------------------------------------------------------------
// 1) replaceAll(chercher, remplacer)  (ECMAScript 2021)
// ------------------------------------------------------------------------
// str-06.js montre que replace() avec une chaîne ne remplace que la 1ère occurrence
// et qu'il fallait utiliser une expression régulière /i/g pour tout remplacer
const chaîne: string = "un chat, deux chats, trois chats";
console.log("replace (1 seule occurrence) :", chaîne.replace("chat", "chien"));
console.log("replaceAll (toutes les occurrences) :", chaîne.replaceAll("chat", "chien"));
// replaceAll accepte aussi une expression régulière, mais celle-ci doit alors avoir le flag [g]
console.log("replaceAll avec regexp :", chaîne.replaceAll(/chat/g, "chien"));

// ------------------------------------------------------------------------
// 2) matchAll(regexp)  (ECMAScript 2020)
// ------------------------------------------------------------------------
// match() avec le flag [g] ne rend que les chaînes trouvées, sans leur position ni leurs groupes
// matchAll() rend un itérateur donnant, pour chaque occurrence, toutes les infos (comme exec())
const texte: string = "Jean a 30 ans, Marie a 25 ans, Paul a 40 ans";
const modèle: RegExp = /(\w+) a (\d+) ans/g;

// avec match (flag g) : on perd le détail des groupes captés
console.log("match =", texte.match(modèle));

// avec matchAll : on garde le détail (nom capté, âge capté, position...)
console.log("-----------------------");
for (const résultat of texte.matchAll(modèle)) {
  console.log("nom =", résultat[1], ", âge =", résultat[2], ", position =", résultat.index);
}

// on peut aussi transformer l'itérateur en tableau grâce à l'opérateur de spread
const tousLesRésultats = [...texte.matchAll(modèle)];
console.log("nombre de correspondances =", tousLesRésultats.length);

// ------------------------------------------------------------------------
// 3) isWellFormed() / toWellFormed()  (ECMAScript 2024)
// ------------------------------------------------------------------------
// une chaîne JavaScript peut contenir un "demi-caractère unicode" invalide isolé
// (par exemple lorsqu'une chaîne a été mal découpée). Cela peut faire planter
// certaines fonctions (encodeURIComponent, ...)
const chaîneValide: string = "café ☕";
const chaîneMalFormée: string = "abc\uD800def"; // \uD800 est un "demi-caractère" isolé, invalide seul

console.log("chaîneValide.isWellFormed() =", chaîneValide.isWellFormed());
console.log("chaîneMalFormée.isWellFormed() =", chaîneMalFormée.isWellFormed());

// toWellFormed() remplace les caractères invalides par le caractère de remplacement �
console.log("chaîneMalFormée.toWellFormed() =", chaîneMalFormée.toWellFormed());
  • [replaceAll] (ES2021) remplace toutes les occurrences d'une chaîne, sans avoir besoin d'une expression régulière /g comme au script str-06 ;
  • [matchAll] (ES2020) rend un itérateur de tous les résultats trouvés, chacun avec ses groupes capturés ET sa position — alors que match avec le flag g ne rend que les chaînes trouvées, sans ce détail ;
  • [isWellFormed]/[toWellFormed] (ES2024) détectent et corrigent les chaînes contenant un « demi-caractère unicode » isolé (souvent dû à un découpage incorrect d'une chaîne), qui peut faire planter certaines fonctions comme encodeURIComponent.
npx tsx strings/str-07.ts

Résultat de l'exécution :

replace (1 seule occurrence) : un chien, deux chats, trois chats
replaceAll (toutes les occurrences) : un chien, deux chiens, trois chiens
replaceAll avec regexp : un chien, deux chiens, trois chiens
match = [ 'Jean a 30 ans', 'Marie a 25 ans', 'Paul a 40 ans' ]
-----------------------
nom = Jean , âge = 30 , position = 0
nom = Marie , âge = 25 , position = 15
nom = Paul , âge = 40 , position = 31
nombre de correspondances = 3
chaîneValide.isWellFormed() = true
chaîneMalFormée.isWellFormed() = false
chaîneMalFormée.toWellFormed() = abcdef