IV. Structures de données : objets et tableaux▲
Ce chapitre sera consacré à la résolution de quelques problèmes simples. En chemin, nous allons étudier deux nouveaux types de valeurs, les tableaux et les objets, et étudier quelques techniques les concernant.
Considérons la situation suivante : votre tante Émilie l'excentrique, dont la rumeur dit qu'elle vit avec cinquante chats (en fait personne n'arrive à les compter), vous envoie régulièrement des e-mails pour vous tenir au courant de ses exploits. Ils sont de la forme suivante :
Mon cher neveu, Ta mère m'a dit que tu t'es mis au parachutisme. Est-ce que c'est vrai ? Fais bien attention à toi, jeune homme ! Tu te souviens de ce qui est arrivé à mon mari ? Et c'était seulement du deuxième étage !
Quoi qu'il en soit, les choses sont très intéressantes de mon côté. J'ai passé toute la semaine à essayer d'attirer l'attention de M. Drake, le gentil monsieur qui a emménagé juste à côté, mais je pense qu'il a peur des chats. À moins qu'il n'y soit allergique ? Je vais essayer de poser Gros Igor sur son épaule la prochaine fois qu'il vient. Je suis curieuse de voir le résultat.
Par ailleurs, l'arnaque dont je t'ai parlé fonctionne mieux que prévu. J'ai déjà récupéré cinq « paiements » et seulement une seule réclamation. Je commence malgré tout à avoir quelques remords. Et puis tu as sans doute raison, ça doit être illégal d'une manière ou d'une autre.
(... etc. ...)
Grosses bises, Tante Émilie
Décédé le 27/04/2006 : Black Leclère
Est né le 05/04/2006 (mère, Lady Pénélope) : Lion Rouge, Docteur Hobbles III, Petit Iroquois
Pour amuser cette vieille dame, vous voudriez garder une trace de la généalogie de ses chats, pour pouvoir ajouter des commentaires comme « P.-S. J'espère que Docteur Hobbles II a bien fêté son anniversaire samedi ! », ou bien « Comment va cette vieille Lady Pénélope ? Elle a cinq ans maintenant, n'est-ce pas ? », en évitant de préférence de demander des nouvelles des chats décédés. Vous avez une grande quantité de vieux e-mails de votre tante et, par chance, elle est très constante dans sa manière de donner les renseignements sur les naissances et décès des chats à la fin de ses e-mails, toujours dans le même format.
Vous n'avez pas envie de parcourir à la main tous ces messages. Heureusement, nous avions justement besoin d'un exemple, nous allons donc écrire un programme qui va faire le travail pour nous. Pour commencer, nous allons écrire un programme qui va nous donner la liste des chats qui sont toujours vivants à la fin du dernier e-mail.
Avant que vous ne posiez la question, au début de cette correspondance, la tante Émilie n'avait qu'un seul chat : Spot (elle était encore assez conformiste à cette époque).
Il est généralement préférable d'avoir une idée de départ sur ce que va faire un programme avant de se mettre à l'écrire... Voici le plan :
- Commencer avec un ensemble de noms de chats ne comprenant que « Spot » ;
- Parcourir chaque e-mail dans l'archive dans l'ordre chronologique ;
- Chercher les paragraphes qui commencent par « Est né le » ou « Décédé le » ;
- Ajouter les noms trouvés dans les paragraphes qui commencent par « Est né le » à l'ensemble de noms ;
- Supprimer les noms de chats trouvés dans les paragraphes qui commencent par « Décédé le » de notre ensemble.
On extraira les noms d'un paragraphe de la façon suivante :
- Trouver le deux-points (:) dans le paragraphe ;
- Prendre la partie après ce signe ;
- Dans cette partie, séparer les noms en cherchant les virgules.
Cet énoncé d'exercice peut rendre nécessaire d'oublier quelques instants les exceptions possibles et d'accepter aveuglément que tante Émilie utilise toujours le même format d'écriture, qu'elle n'oublie jamais un nom de chat, ni ne fait de faute de frappe. Mais votre tante est comme ça et ça tombe bien pour nous.
D'abord, je vais vous expliquer les propriétés. Beaucoup de valeurs en JavaScript ont d'autres valeurs qui leur sont associées. Ces associations sont appelées propriétés. Chaque chaîne de caractères a une propriété appelée length (longueur), qui correspond à un nombre, la quantité de caractères dans cette chaîne.
On peut accéder aux propriétés de deux manières :
var texte =
"brume pourpre"
;
show
(
texte[
"length"
]
);
show
(
texte.
length);
La deuxième manière est un raccourci de la première et ne fonctionne que lorsque le nom de la propriété s'écrit comme un nom de variable - lorsqu'il n'y a pas d'espace ou de symbole et lorsqu'elle ne commence pas par un chiffre.
Les valeurs null et undefined n'ont pas de propriété. Essayer de lire des propriétés de ces valeurs donnera une erreur. Essayez le code suivant, juste pour voir le type de message d'erreur que votre navigateur va retourner dans ce cas de figure (dans certains navigateurs, ce message sera assez mystérieux).
Les propriétés d'une chaîne de caractères ne peuvent pas être changées. Elles sont plus nombreuses que la seule longueur length, comme nous allons le voir, mais vous ne pouvez ajouter ni supprimer aucune propriété.
C'est différent avec les valeurs du type object. Leur rôle principal est de conserver d'autres valeurs. Ils ont, en quelque sorte, leur propre jeu de « tentacules » sous forme de propriétés. Vous pouvez les modifier, les supprimer ou en ajouter de nouvelles.
Un objet peut s'écrire de la façon suivante :
var chat =
{
couleur
:
"gris"
,
nom
:
"Spot"
,
taille
:
46
};
chat.
taille =
47
;
show
(
chat.
taille);
delete chat.
taille;
show
(
chat.
taille);
show
(
chat);
Comme les variables, chaque propriété attachée à un objet a un nom sous forme d'une chaîne de caractères. La première instruction crée un objet dans lequel la propriété "couleur" contient la chaîne "gris", la propriété "nom" est liée à la chaîne "Spot", et la propriété "taille" fait référence au nombre 46. La deuxième ligne modifie la propriété taille en lui donnant une nouvelle valeur, ce qui se fait de la même manière que pour la modification d'une variable.
Le mot-clé delete supprime les propriétés. Essayer de lire une propriété qui n'existe pas donnera la valeur undefined.
Si une propriété qui n'existe pas encore est affectée avec l'opérateur =, elle est ajoutée à l'objet.
var vide =
{};
vide.
plusVraiment =
1000
;
show
(
vide.
plusVraiment);
Les propriétés dont le nom ne pourrait pas être une variable doivent être mises entre guillemets au moment de la création de l'objet et utilisées avec des parenthèses :
var truc =
{
"gabba gabba"
:
"hey"
,
"5"
:
10
};
show
(
truc[
"5"
]
);
truc[
"5"
]
=
20
;
show
(
truc[
2
+
3
]
);
delete truc[
"gabba gabba"
];
Comme vous pouvez le voir, on peut mettre n'importe quelle expression entre les parenthèses. Elle sera convertie dans une chaîne pour définir le nom de la propriété. On peut aussi utiliser des variables pour donner un nom à une propriété :
L'opérateur in peut servir à tester si un objet possède une certaine propriété. Son résultat est un booléen.
var poupeeRusse =
{};
poupeeRusse.
contenu =
poupeeRusse;
show
(
"contenu"
in poupeeRusse);
show
(
"contenu"
in poupeeRusse.
contenu);
Quand les valeurs d'un objet sont affichées sur la console, on peut cliquer à la souris pour inspecter leurs propriétés. La fenêtre de sortie devient une fenêtre « inspecteur ». Le petit « x » en haut à droite s'utilise pour retourner à la fenêtre de sortie et la flèche gauche permet de retourner aux propriétés de l'objet inspecté.
show
(
poupeeRusse);
La solution pour le problème des chats passe par un ensemble de noms. Un ensemble (ou « set ») est un groupe de valeurs dans lequel aucune valeur ne peut apparaître plus d'une fois. Si les noms de chats sont des chaînes de caractères, pouvez-vous imaginer une façon pour qu'un objet devienne un ensemble de noms ?
Écrivez maintenant la façon dont un nom peut être ajouté à cet ensemble, comment on peut le supprimer et comment on peut vérifier si un certain nom est bien présent dans l'ensemble.
Une solution consiste à mémoriser le contenu de l'ensemble sous la forme de propriétés d'un objet. Pour ajouter un nom, on crée une propriété avec ce nom en lui affectant une valeur, n'importe laquelle. Pour supprimer un nom, on supprimera la propriété de l'objet. L'opérateur in sera utilisé pour savoir si une certaine propriété fait partie de l'ensemble(10).
var set =
{
"Spot"
:
true};
// Ajoute "Croc Blanc" à l'ensemble
set[
"Croc Blanc"
]
=
true;
// Supprime "Spot"
delete set[
"Spot"
];
// Regarde si "Asoka" est dans l'ensemble
show
(
"Asoka"
in set);
Les valeurs des objets peuvent apparemment changer. Les types de valeurs vues dans le chapitre 2 sont toutes invariables, il n'est pas possible de changer une valeur existante pour ces types de données. Vous pouvez les associer ou en tirer de nouvelles valeurs, mais lorsque vous prenez une chaîne de caractères particulière, le texte à l'intérieur ne peut pas être modifié. Avec les objets, d'un autre côté, le contenu d'une valeur peut être modifié en changeant ses propriétés.
Lorsque nous considérons deux nombres, 120 et 120, il est possible dans tous les cas pratiques de les considérer comme des nombres identiques. Avec des objets, il y a une différence importante entre avoir deux « références » du même objet et avoir deux objets distincts qui possèdent les mêmes propriétés. Considérons le code suivant :
var objet1 =
{
valeur
:
10
};
var objet2 =
objet1;
var objet3 =
{
valeur
:
10
};
show
(
objet1 ==
objet2);
show
(
objet1 ==
objet3);
objet1.
valeur =
15
;
show
(
objet2.
valeur);
show
(
objet3.
valeur);
objet1 et objet2 sont deux variables attachées à la même valeur. Il n'y a en fait qu'un seul objet, c'est pourquoi en changeant objet1 on change également la valeur de objet2. La variable objet3 pointe vers un autre objet qui contient au départ la même propriété que objet1 mais elle a une existence distincte.
L'opérateur JavaScript ==, lorsqu'il compare des objets, ne retournera la valeur booléenne true que si chacune des valeurs qu'on lui donne à comparer sont exactement les mêmes. Comparer des objets différents ayant des contenus identiques donnera le résultat false. C'est utile dans certaines situations, mais peu adapté à d'autres.
Les valeurs d'un objet peuvent jouer de nombreux rôles. Se comporter comme un ensemble n'est que l'un d'entre eux. Nous allons voir d'autres utilisations dans ce chapitre et le chapitre 8 montrera d'autres façons importantes d'utiliser les objets.
Dans le plan d'action pour le problème des chats - en fait, appelons-le un algorithme au lieu d'un plan, cela nous donne l'impression qu'on sait de quoi on parle - dans l'algorithme, on parle de parcourir chaque e-mail contenu dans une archive. Mais comment se présente cette archive ? Et d'où vient-elle ?
Ne vous inquiétez pas de la deuxième question pour le moment. Le chapitre 14 explique quelques-unes des possibilités pour importer des données dans vos programmes. Pour l'instant, on dira que les e-mails sont déjà là, comme par magie. La magie est parfois très facile, avec les ordinateurs.
La façon dont l'archive est enregistrée reste une question pertinente. Elle contient quantité d'e-mails. Un e-mail peut être vu comme une chaîne de caractères, c'est évident. Toute l'archive pourrait être mise dans une énorme chaîne de caractères mais ce ne serait pas pratique. Ce qu'il nous faut, c'est une structure de chaînes de caractères distinctes.
Les objets sont justement utilisés pour structurer des choses. On pourrait très bien créer un objet comme celui-ci :
var archiveDeMails =
{
"le premier e-mail"
:
"Mon cher neveu, ?"
,
"le deuxième e-mail"
:
"?"
/* et ainsi de suite? */
};
Mais parcourir les e-mails du début à la fin serait difficile - comment le programme peut-il deviner le nom de ces propriétés ? La solution est d'utiliser des noms de propriétés plus prévisibles :
var archiveDeMails =
{
0
:
"Mon cher neveu, ? (mail numéro 1)"
,
1
:
"(mail numéro 2)"
,
2
:
"(mail numéro 3)"
};
for (
var courant =
0
;
courant in archiveDeMails;
courant++
)
print
(
"Traitement de l'e-mail #"
,
courant,
": "
,
archiveDeMails[
courant]
);
La chance veut qu'il existe un type d'objet particulier qui corresponde exactement à ce type de besoin. Ce sont les tableaux et ils fournissent des commodités très utiles, par exemple length (longueur), une propriété qui contient le nombre d'éléments dans le tableau et bien d'autres fonctions utiles pour ce type de structure.
Pour créer de nouveaux tableaux, on utilise des crochets ([ et ]):
var archiveDeMails =
[
"mail un"
,
"mail deux"
,
"mail trois"
];
for (
var courant =
0
;
courant <
archiveDeMails.
length;
courant++
)
print
(
"Traitement de l'e-mail #"
,
courant,
": "
,
archiveDeMails[
courant]
);
Dans cet exemple, le nombre d'éléments n'est plus spécifié explicitement. Le premier a automatiquement le numéro 0, le deuxième le numéro 1 et ainsi de suite.
Pourquoi commencer à 0 ? Dans la vie courante, on compte d'habitude à partir de 1. Aussi étrange que cela paraisse, la numérotation à partir de 0 est souvent plus pratique pour programmer. Faites avec pour l'instant, vous allez vous y faire.
Commencer par l'élément 0 veut aussi dire que dans une structure qui a X éléments, le dernier élément sera trouvé à la position X - 1. C'est pourquoi la boucle for dans notre exemple teste la valeur current < archiveDeMails.length. Il n'y a pas d'élément à la position archiveDeMails.length, donc dès que current atteint cette valeur, on arrête la boucle.
Écrivez une fonction nommée serie qui prend un argument, un nombre positif et retourne un tableau contenant chaque nombre de 0 jusqu'au nombre donné en paramètre inclus.
Un tableau vide peut être créé en tapant simplement []. Souvenez-vous que pour ajouter des propriétés à un tableau, comme pour un objet, il suffit d'affecter une valeur à la propriété avec l'opérateur =. La propriété length est mise à jour automatiquement quand des éléments sont ajoutés.
function serie
(
max) {
var resultat =
[];
for (
var i =
0
;
i <=
max;
i++
)
resultat[
i]
=
i;
return resultat;
}
show
(
serie
(
4
));
Au lieu de nommer la variable de boucle compteur ou courant, comme je l'ai fait jusqu'à présent, elle s'appelle désormais simplement i. L'utilisation d'une seule lettre, habituellement i, j ou k pour les variables de boucle est une habitude très répandue en programmation. Son origine tient presque à de la paresse : on préfère taper un caractère que sept et des noms comme compteur et courant ne donnent pas forcément plus d'informations sur la variable.
Si un programme utilise trop souvent des variables à un seul caractère, sans explication, il peut devenir très difficile à comprendre. Dans mes propres programmes, j'essaie de me limiter à quelques cas de figure seulement. Les petites boucles font partie de ces cas. Si la boucle contient une autre boucle et que celle-ci utilise aussi une variable appelée i, la boucle intérieure va modifier la variable dont se sert la première boucle, et rien ne va fonctionner. On pourrait utiliser j pour la boucle intérieure, mais en général, lorsque le corps d'une boucle est grand, vous devriez utiliser un nom de variable ayant une signification utile pour la compréhension.
Les objets chaînes de caractères et tableaux contiennent tous deux, outre la propriété length, un certain nombre d'autres propriétés qui font référence à des fonctions.
Chaque chaîne de caractères a une propriété toUpperCase. Lorsqu'elle est appelée, elle retourne une copie de la chaîne, transformée avec chaque lettre en majuscule. Il y a aussi l'équivalent toLowerCase. Devinez le résultat...
Remarquez que même si l'appel de toUpperCase se fait sans arguments, la fonction a malgré tout accès au contenu de la chaîne de caractères "Doh", la valeur dont elle est une propriété. La façon dont cela fonctionne est décrite dans le chapitre 8.
Les propriétés qui se comportent comme des fonctions sont généralement appelées méthodes, ainsi, toUpperCase est une méthode des objets chaînes de caractères.
var flipper =
[];
flipper.push
(
"Flipper"
);
flipper.push
(
"le"
);
flipper.push
(
"dauphin"
);
show
(
flipper.join
(
" "
));
show
(
flipper.pop
(
));
show
(
flipper);
La méthode push, associée aux tableaux, peut être utilisée pour ajouter des valeurs à ceux-ci. Nous aurions pu l'utiliser dans l'exercice précédent, à la place de resultat[i] = i. Il y a aussi la méthode pop, complémentaire de push : elle supprime le dernier élément d'un tableau et retourne sa valeur. join construit une seule chaîne de caractères à partir d'un tableau de chaînes de caractères. Le paramètre utilisé avec cette méthode sera inséré entre chaque valeur du tableau, avant l'assemblage de la chaîne de caractères finale.
Revenons à nos chats : nous savons maintenant qu'utiliser un tableau serait une bonne idée pour ranger les archives des e-mails. Sur cette page, la fonction recupererLesMails sera utilisée pour récupérer (magiquement) ce tableau. Parcourir les e-mails qu'il contient pour les traiter un par un devient simple comme un jeu d'enfant :
var archiveDeMails =
recupererLesMails
(
);
for (
var i =
0
;
i <
archiveDeMails.
length;
i++
) {
var email =
archiveDeMails[
i];
print
(
"Traitement de l'e-mail #"
,
i);
// Faire plus de choses?
}
Nous avons également décidé d'une manière de représenter un ensemble de chats vivants. Le problème qui reste à traiter, cependant, est celui de détecter des paragraphes d'un e-mail qui contiennent "Est né le" ou "Décédé le".
La première question qui vient à l'esprit est de savoir ce qu'est un paragraphe au juste. Dans ce cas, la valeur de la chaîne elle-même n'est pas d'une grande utilité : le concept du texte en JavaScript ne va guère plus loin que l'idée de « suite de caractères », si bien que nous devons définir les paragraphes de cette façon.
Nous avons vu plus haut qu'il existe une chose qui s'appelle un caractère de fin de ligne. C'est ce que la plupart des gens utilisent pour séparer les paragraphes. Nous considérons donc un paragraphe comme une partie de mail qui commence par un caractère saut de ligne ou au début du contenu du message et se termine au caractère saut de ligne suivant ou bien à la fin du contenu.
Et nous n'avons même pas à écrire nous-mêmes l'algorithme pour scinder une chaîne en paragraphes. Les chaînes ont déjà une méthode appelée split, qui est (pratiquement) l'inverse de la méthode join pour les tableaux. Elle découpe une chaîne en un tableau en utilisant la chaîne fournie comme argument pour déterminer à quel endroit opérer les divisions en paragraphes.
var mots =
"Les villes de l'arrière-pays"
;
show
(
mots.split
(
" "
));
Ainsi, découper avec des caractères saut de ligne ("\n") est une méthode utilisable pour diviser un e-mail en paragraphes.
split et join ne sont pas exactement l'inverse l'une de l'autre. string.split(x).join(x) produit toujours la valeur originale, mais pas array.join(x).split(x). Pouvez-vous donner un exemple de tableau dans lequel .join(" ").split(" ") produit une valeur différente ?
var tableau =
[
"a"
,
"b"
,
"c d"
];
show
(
tableau.join
(
" "
).split
(
" "
));
Les paragraphes qui ne commencent ni par « Né le » ni par « Décédé le » peuvent être ignorés par le programme. Comment peut-on tester si une chaîne commence par un mot particulier ? On peut utiliser la méthode charAt pour obtenir un caractère particulier dans une chaîne. x.charAt(0) donne le premier caractère, 1 est le deuxième et ainsi de suite. Voici une façon de vérifier si une chaîne commence par « Né le» :
var paragraphe =
"Est né le 15/11/2003 (mère, Spot) : Croc Blanc"
;
show
(
paragraphe.charAt
(
0
) ==
"E"
&&
paragraphe.charAt
(
1
) ==
"s"
&&
paragraphe.charAt
(
2
) ==
"t"
&&
paragraphe.charAt
(
3
) ==
" "
&&
paragraphe.charAt
(
4
) ==
"n"
&&
paragraphe.charAt
(
5
) ==
"é"
&&
paragraphe.charAt
(
6
) ==
" "
&&
paragraphe.charAt
(
7
) ==
"l"
&&
paragraphe.charAt
(
8
) ==
"e"
);
Mais cela devient un peu pénible - imaginez que vous devez vérifier la présence d'un mot de 30 caractères. On peut cependant en tirer une leçon utile : si une ligne est démesurément longue, on peut l'étendre sur plusieurs lignes. Le résultat peut être plus facile à lire en alignant le début d'une nouvelle ligne avec la ligne originale jouant le même rôle
Les chaînes possèdent également une méthode nommée slice. Elle permet de copier un morceau de la chaîne de caractères, en commençant par le caractère à la position donnée par le premier argument, et se terminant avant le caractère (non inclus) à la position donnée par le second argument. Cela permet de vérifier une chaîne de caractères en peu de lignes.
show
(
paragraphe.slice
(
0
,
9
) ==
"Est né le"
);
Écrivez une fonction nommée chaineCommencePar qui prend deux arguments, tous les deux des chaînes de caractères. Elle renvoie true quand le premier argument commence avec les caractères du second argument, sinon elle renvoie false.
function chaineCommencePar
(
chaine,
motif) {
return chaine.slice
(
0
,
motif.
length) ==
motif;
}
show
(
chaineCommencePar
(
"rotation"
,
"rot"
));
Que se passe-t-il quand charAt ou slice sont utilisés pour prendre un fragment de chaîne qui n'existe pas ? Est-ce que la fonction chaineCommencePar que j'ai montrée va encore fonctionner si la chaîne recherchée est plus longue que celle dans laquelle on cherche ?
show
(
"Ouai"
.charAt
(
250
));
show
(
"Nan"
.slice
(
1
,
10
));
charAt va renvoyer "" s'il n'existe pas de caractère à la position donnée et slice va tout simplement laisser tomber la partie de la nouvelle chaîne qui n'existe pas.
Cela confirme que cette version de chaineCommencePar fonctionne. Quand la fonction chaineCommencePar("Idiots","Mes très chers collègues") est appelée, l'appel à slice renverra toujours une chaîne plus courte que pattern, parce que string ne comporte pas assez de caractères. C'est pour cette raison que la comparaison avec == renverra false, ce qui est correct.
C'est une bonne idée de toujours consacrer un moment pour prendre en considération les entrées aberrantes (mais valides) dans un programme. On les appelle en général des cas imprévus et il est très fréquent qu'un programme qui tourne à merveille avec toutes les entrées « normales » se plante complètement avec des cas imprévus.
La seule partie de notre problème de chats qui ne soit pas encore résolue est l'extraction des noms d'un paragraphe. L'algorithme était le suivant :
- Trouver le deux-points (:) dans le paragraphe ;
- Prendre la partie après ce signe ;
- Dans cette partie, séparer les noms en cherchant les virgules.
Il faut reproduire cela à la fois pour les paragraphes qui commencent par Décédé le et ceux qui commencent par Est né le. Ce serait une bonne idée de le mettre dans une fonction, de sorte que les deux parties de code qui gèrent les différentes sortes de paragraphes puissent l'utiliser.
Savez-vous écrire une fonction nomDesChats qui prenne un paragraphe comme argument et renvoie un tableau de noms ?
Les chaînes ont une méthode indexOfque l'on peut utiliser pour trouver la (première) position d'un caractère ou une sous-chaîne à l'intérieur d'une chaîne. De même si on ne donne qu'un seul argument à slice, elle renverra la partie de la chaîne depuis la première position jusqu'à son extrémité.
Il peut être pratique d'utiliser la console pour « explorer » les fonctions. Par exemple, tapez "foo: bar".indexOf(":") et voyez ce qui se passe. (NDT Les mots foo et bar n'ont pas de signification précise, et illustrent parfois des exemples de code).
function nomDesChats
(
paragraphe) {
var deuxPoints =
paragraphe.indexOf
(
":"
);
return paragraphe.slice
(
deuxPoints +
2
).split
(
", "
);
}
show
(
nomDesChats
(
"Est né le 20/09/2004 (mère, Bess la Jaune): "
+
"Docteur Hobbles II, Kaïra"
));
La partie la plus délicate qui est ignorée par la description originale de l'algorithme, est le traitement des espaces après les deux-points et les virgules. Le +2, utilisé pour le découpage de chaînes, est nécessaire pour laisser de côté le deux-points lui-même et l'espace qui le suit. L'argument pour split contient à la fois une virgule et un espace, parce que ce sont les séparateurs de noms, plutôt que par une simple virgule.
Cette fonction n'effectue aucune vérification de problèmes éventuels. Nous faisons comme si, dans ce cas précis, l'entrée était toujours correcte.
Tout ce qui nous reste à faire maintenant, c'est de rassembler les pièces du puzzle. Voici une façon de s'y prendre :
var archiveDeMails =
recupererLesMails
(
);
var chatsVivants =
{
"Spot"
:
true};
for (
var mail =
0
;
mail <
archiveDeMails.
length;
mail++
) {
var paragraphes =
archiveDeMails[
mail]
.split
(
"
\n
"
);
for (
var paragraphe =
0
;
paragraphe <
paragraphes.
length;
paragraphe++
) {
if (
chaineCommencePar
(
paragraphes[
paragraphe],
"Est né le"
)) {
var noms =
nomDesChats
(
paragraphes[
paragraphe]
);
for (
var nom =
0
;
nom <
noms.
length;
nom++
)
chatsVivants[
noms[
nom]]
=
true;
}
else if (
chaineCommencePar
(
paragraphes[
paragraphe],
"Décédé le"
)) {
var noms =
nomDesChats
(
paragraphes[
paragraphe]
);
for (
var nom =
0
;
nom <
noms.
length;
noms++
)
delete chatsVivants[
noms[
nom]];
}
}
}
show
(
chatsVivants);
Voilà un bloc de code assez copieux et dense. Nous allons voir tout de suite comment l'alléger un peu. Mais d'abord, jetons un coup d'œil aux résultats. Nous savons comment vérifier si un chat particulier a survécu :
if (
"Spot"
in chatsVivants)
print
(
"Spot est vivant !"
);
else
print
(
"Ce bon vieux Spot, qu'il repose en paix."
);
Mais comment allons-nous faire pour dresser la liste de tous les chats vivants ? Le mot-clé in a une signification légèrement différente lorsqu'il est utilisé avec for :
Une boucle comme celle-là va parcourir les noms des propriétés d'un objet, ce qui nous permettra d'énumérer tous les noms de notre ensemble.
Certaines parties de code ressemblent à une jungle impénétrable. L'exemple de solution pour le problème des chats souffre de ce défaut. Une façon de ménager des clairières consiste tout simplement à ajouter des lignes vides. Cela améliore la lisibilité, mais ne résout pas véritablement le problème.
Ce qu'il nous faut ici, c'est casser le code. Nous avons déjà écrit deux fonctions d'aide, chaineCommencePar et nomDesChats, qui toutes deux résolvent une petite partie du problème de façon compréhensible. Continuons sur cette lancée.
function ajouterAuSet
(
set,
valeurs) {
for (
var i =
0
;
i <
valeurs.
length;
i++
)
set[
valeurs[
i]]
=
true;
}
function enleverDuSet
(
set,
valeurs) {
for (
var i =
0
;
i <
valeurs.
length;
i++
)
delete set[
valeurs[
i]];
}
Ces deux fonctions traitent de l'ajout et de la suppression des noms dans l'ensemble. Ce qui supprime déjà les deux plus importantes boucles internes de la solution :
var chatsVivants =
{
Spot
:
true};
for (
var mail =
0
;
mail <
archiveDeMails.
length;
mail++
) {
var paragraphes =
archiveDeMails[
mail]
.split
(
"
\n
"
);
for (
var paragraphe =
0
;
paragraphe <
paragraphes.
length;
paragraphe++
) {
if (
chaineCommencePar
(
paragraphes[
paragraphe],
"Est né le"
))
ajouterAuSet
(
chatsVivants,
nomDesChats
(
paragraphes[
paragraphe]
));
else if (
chaineCommencePar
(
paragraphes[
paragraphe],
"Décédé le"
))
enleverDuSet
(
chatsVivants,
nomDesChats
(
paragraphes[
paragraphe]
));
}
}
C'est un sacré progrès, si je peux me permettre. Pourquoi ajouterAuSet et enleverDuSet prennent-ils l'ensemble comme argument ? Ils pourraient utiliser la variable chatsVivants directement, s'ils le voulaient. La raison, c'est que de cette façon elles ne sont pas totalement liées à notre problème. Si ajouterAuSet changeait directement chatsVivants, il faudrait l'appeler ajouterChatsDansEnsembleDeChats ou quelque chose comme ça. Tel que nous l'utilisons, c'est un outil utile pour des cas plus généraux.
Même si nous ne devions jamais utiliser ces fonctions pour quoi que ce soit d'autre, ce qui est très probable, il est utile de les décrire de cette façon. Car elles se « suffisent à elles-mêmes », on peut les lire et les comprendre, sans avoir besoin de connaître une variable externe nommée chatsVivants.
Ces fonctions ne sont pas pures : elles modifient l'objet set qui a été passé en premier argument. Cela rend les choses un peu plus délicates qu'avec des fonctions pures mais c'est déjà beaucoup moins perturbant que des fonctions qui perdent les pédales et modifient les valeurs de variables comme ça leur chante.
Nous continuons à découper l'algorithme en petites unités :
function trouverChatsVivants
(
) {
var archiveDeMails =
recupererLesMails
(
);
var chatsVivants =
{
"Spot"
:
true};
function traiterParagraphe
(
paragraphe) {
if (
chaineCommencePar
(
paragraphe,
"Est né le"
))
ajouterAuSet
(
chatsVivants,
nomDesChats
(
paragraphe));
else if (
chaineCommencePar
(
paragraphe,
"Décédé le"
))
enleverDuSet
(
chatsVivants,
nomDesChats
(
paragraphe));
}
for (
var mail =
0
;
mail <
archiveDeMails.
length;
mail++
) {
var paragraphes =
archiveDeMails[
mail]
.split
(
"
\n
"
);
for (
var i =
0
;
i <
paragraphes.
length;
i++
)
traiterParagraphe
(
paragraphes[
i]
);
}
return chatsVivants;
}
var combien =
0
;
for (
var chat in trouverChatsVivants
(
))
combien++;
print
(
"Il y a "
,
combien,
" chats."
);
La totalité de l'algoritme est encapsulée dans une fonction. Cela signifie qu'elle ne laisse rien traîner en vrac derrière elle après exécution : chatsVivants est maintenant une variable locale dans la fonction et non plus une variable globale, si bien qu'elle n'existe que pendant que la fonction s'exécute. Le code qui a besoin de cet ensemble peut appeler trouverChatsVivants et utiliser la valeur qu'il renvoie.
Il me semble que faire de traiterParagraphe une fonction distincte peut aussi clarifier les choses. Mais celle-ci est si étroitement liée à l'algorithme-des-chats qu'elle n'aurait aucun sens dans une autre situation. De plus, elle a besoin d'accéder à la variable chatsVivants. C'est donc une candidate parfaite pour devenir une fonction à l'intérieur d'une fonction. Quand elle existe à l'intérieur de trouverChatsVivants, il est clair qu'elle n'est pertinente que là et qu'elle a accès aux variables de sa fonction parente.
Cette solution est en fait plus grande que la précédente. Mais elle est plus propre et j'espère que vous reconnaîtrez qu'elle est plus lisible.
Le programme ignore encore un grand nombre d'informations qui sont incluses dans les mails. Il s'agit des dates de naissance, de mort et des noms des mères.
Commençons avec les dates : quelle pourrait être la meilleure façon de stocker une date ? Nous pourrions créer un objet avec ces trois propriétés, year, month, et day et stocker ensuite des nombres à l'intérieur.
var quand =
{
year
:
1980
,
month
:
2
,
day
:
1
};
Mais JavaScript fournit déjà une sorte d'objet pour cela. Un tel objet peut être créé en utilisant le mot-clé new:
Tout comme la notation avec les accolades et les deux-points que nous avons déjà vue, new est une façon de créer des valeurs d'un objet. Au lieu de préciser tous les noms de propriétés et les valeurs, une fonction est utilisée pour créer l'objet. Cela rend possible de définir une sorte de procédure standard pour créer des objets. Les fonctions comme celle-là s'appellent constructeurs et nous verrons comment les écrire dans chapitre 8.
Le constructeur Date peut être utilisé de différentes manières
Comme vous pouvez le voir, ces objets peuvent enregistrer l'heure d'un jour aussi bien qu'une date. Quand aucun argument n'est précisé, un objet représentant l'heure et la date actuelles est créé. Des arguments peuvent être précisés pour stocker une heure et une date précises. L'ordre des arguments est l'année, le mois, le jour, l'heure, la minute, la seconde puis la milliseconde. Les quatre derniers arguments sont optionnels et définis à 0 s'ils ne sont pas précisés.
Pour décrire les mois, on utilise la numérotation de 0 à 11, qui peut provoquer une confusion. Surtout que les nombres définissant les jours commencent eux à 1.
Le contenu de l'objet Date peut être inspecté avec un nombre de méthodes get....
var aujourdhui =
new Date(
);
print
(
"Année : "
,
aujourdhui.getFullYear
(
),
", mois : "
,
aujourdhui.getMonth
(
),
", jour : "
,
aujourdhui.getDate
(
));
print
(
"Heure : "
,
aujourdhui.getHours
(
),
", minutes : "
,
aujourdhui.getMinutes
(
),
", secondes: "
,
aujourdhui.getSeconds
(
));
print
(
"Jour de la semaine : "
,
aujourdhui.getDay
(
));
Tous ces éléments, excepté la méthode getDay, ont une variable set... qui peut être utilisée pour modifier la valeur de l'objet date.
Dans l'objet, une date est représentée par la somme de millisecondes cumulée depuis le 1er janvier 1970. Vous pouvez imaginer que c'est un nombre assez impressionnant.
Une chose très utile à faire avec les dates, c'est de les comparer.
var chuteDuMur =
new Date(
1989
,
10
,
9
);
var premiereGuerreDuGolf =
new Date (
1990
,
6
,
2
);
show
(
chuteDuMur <
premiereGuerreDuGolf);
show
(
chuteDuMur ==
chuteDuMur);
// mais
show
(
chuteDuMur ==
new Date(
1989
,
10
,
9
));
Comparer les dates avec <, >, <= et >= remplit exactement l'office que nous voulons en faire. Quand un objet date est comparé avec lui-même, le résultat est true, ce qui est bien également. Mais quand == est utilisé pour comparer un objet date à un autre objet date différent mais de même valeur, on obtient false. Étrange, non ?
Comme précisé plus tôt, == retournera la valeur false
var chuteDuMur1 =
new Date(
1989
,
10
,
9
),
chuteDuMur2 =
new Date(
1989
,
10
,
9
);
show
(
chuteDuMur1.getTime
(
) ==
chuteDuMur2.getTime
(
));
Au-delà de la date et l'heure, l'objet Date contient aussi des informations sur le fuseau horaire. Quand il est une heure à Amsterdam, en fonction de la période de l'année il peut être midi à Londres et sept heures du matin à New York. De telles heures ne peuvent être rapprochées que si vous prenez les fuseaux horaires en compte. La fonction getTimezoneOffset d'une Date peut être utilisée pour trouver de combien de minutes elle s'éloigne du GMT (Heure du méridien de Greenwich)
"Décédé le 27/04/2006 : Black Leclère"
La partie date est toujours exactement à la même place du paragraphe. Comme c'est pratique. Écrivez une fonction extraireDate qui prend un tel paragraphe pour argument, extrait la date et la renvoie sous la forme d'un objet date.
function extraireDate
(
paragraphe) {
function nombreEnPosition
(
position,
longueur) {
return Number(
paragraphe.slice
(
position,
position +
longueur));
}
return new Date(
nombreEnPosition
(
16
,
4
),
nombreEnPosition
(
13
,
2
) -
1
,
nombreEnPosition
(
10
,
2
));
}
show
(
extraireDate
(
"Décédé le 27-04-2006 : Black Leclère"
));
Cela ne marcherait pas sans les appels à Number, mais comme je l'ai expliqué plus haut, je préfère ne pas utiliser de chaînes comme si elles étaient des nombres. La fonction interne a été introduite pour éviter d'avoir à répéter trois fois les parties Number et slice.
Notez le -1 pour le numéro du mois. Comme la plupart des gens, tante Émilie compte les mois à partir de 1, nous devons donc ajuster cette valeur avant de donner la Date au constructeur (le numéro du jour ne relève pas du même problème, puisque les objets Date comptent les jours de la façon humaine habituelle).
Dans le chapitre 10, nous verrons une façon plus pratique et plus sûre d'extraire des parties de chaînes qui ont une structure déterminée.
Stocker des chats est une opération qui va se dérouler différemment à partir de maintenant. Au lieu de simplement mettre la valeur true sur l'ensemble, nous stockons un objet avec les informations sur le chat. Lorsqu'un chat meurt, nous ne le supprimons pas de l'ensemble, nous ajoutons simplement la propriété deces à l'objet pour stocker la date à laquelle le pauvre animal a trépassé.
Cela signifie que nos fonctions ajouterAuSet et enleverDuSet sont devenues inutiles. Quelque chose de comparable est nécessaire, mais il s'agit de stocker aussi les dates de naissance et par la suite, les noms des mères.
function enregistrementChat
(
nom,
dateNaissance,
mere) {
return {
nom
:
nom,
naissance
:
dateNaissance,
mere
:
mere};
}
function ajouterChats
(
set,
noms,
dateNaissance,
mere) {
for (
var i =
0
;
i <
noms.
length;
i++
)
set[
noms[
i]]
=
enregistrementChat
(
noms[
i],
dateNaissance,
mere);
}
function chatsDecedes
(
set,
noms,
dateDeces) {
for (
var i =
0
;
i <
noms.
length;
i++
)
set[
noms[
i]].
deces =
dateDeces;
}
enregistrementChat est une fonction distincte pour créer ces objets de stockage. Elle pourrait être utile dans d'autres situations, telles que la création d'un objet pour Spot. « Record » (« enregistrement » en français) est le terme qu'on emploie couramment pour des objets de ce type, qui sont utilisés pour regrouper un nombre limité de valeurs.
Essayons donc maintenant d'extraire les noms des mamans chats qui se trouvent dans des paragraphes.
"Est né le 15/11/2003 (mère, Spot): Croc Blanc"
Voici un moyen d'obtenir cela...
function extraireNomMere
(
paragraphe) {
var start =
paragraphe.indexOf
(
"(mère, "
) +
"(mère, "
.
length;
var end =
paragraphe.indexOf
(
")"
);
return paragraphe.slice
(
start,
end);
}
show
(
extraireNomMere
(
"Est né le 15/11/2003 (mère, Spot): Croc Blanc"
));
Notez comment la position de départ a dû être ajustée à la longueur de "(mère, ", parce que indexOf renvoie la position initiale de la chaîne et non la finale.
Ce que fait extraireNomMere peut être exprimé d'une façon plus générale. Écrivez une fonction extraireChaineEntre qui prend trois arguments, qui seront tous des chaînes. Elle renverra la partie du premier argument qui apparaît entre les chaînes fournies par le deuxième et le troisième arguments.
Ainsi, extraireChaineEntre("Est né le 15/11/2003 (mère, Spot): Croc Blanc", "(mère, ", ")") donne "Spot".
extraireChaineEntre("bu ] boo [ bah ] gzz", "[ ", " ]") renvoie "bah".
Pour faire marcher ce deuxième exemple, il peut être utile de savoir qu'on peut attribuer à indexOf un second paramètre facultatif qui précise à partir de quel point doit commencer la recherche.
function extraireChaineEntre
(
chaine,
debut,
fin) {
var indexDebut =
chaine.indexOf
(
debut) +
debut.
length;
var indexFin =
chaine.indexOf
(
fin,
indexDebut);
return chaine.slice
(
indexDebut,
indexFin);
}
show
(
extraireChaineEntre
(
"bu ] boo [ bah ] gzz"
,
"[ "
,
" ]"
));
Avoir la fonction extraireChaineEntre rend possible l'expression de extraireNomMere de façon plus simple :
function extraireNomMere
(
paragraphe) {
return extraireChaineEntre
(
paragraphe,
"(mère, "
,
")"
);
}
Le nouvel algorithme-des-chats amélioré ressemble maintenant à ça :
function trouverChats
(
) {
var archiveDeMails =
recupererLesMails
(
);
var chats =
{
"Spot"
:
enregistrementChat
(
"Spot"
,
new Date(
1997
,
2
,
5
),
"inconnue"
)};
function traiterParagraphe
(
paragraphe) {
if (
chaineCommencePar
(
paragraphe,
"Est né le"
))
ajouterChats
(
chats,
nomDesChats
(
paragraphe),
extraireDate
(
paragraphe),
extraireNomMere
(
paragraphe));
else if (
chaineCommencePar
(
paragraphe,
"Décédé le"
))
chatsDecedes
(
chats,
nomDesChats
(
paragraphe),
extraireDate
(
paragraphe));
}
for (
var mail =
0
;
mail <
archiveDeMails.
length;
mail++
) {
var paragraphes =
archiveDeMails[
mail]
.split
(
"
\n
"
);
for (
var i =
0
;
i <
paragraphes.
length;
i++
)
traiterParagraphe
(
paragraphes[
i]
);
}
return chats;
}
var tousLesChats =
trouverChats
(
);
Avoir ces données supplémentaires nous permet d'avoir finalement une idée plus précise des chats dont parle tante Émilie. Une fonction comme celle-ci pourrait être utile :
function formatDate
(
date) {
return date.getDate
(
) +
"/"
+ (
date.getMonth
(
) +
1
) +
"/"
+
date.getFullYear
(
);
}
function renseignementSurChat
(
data,
nom) {
if (!(
nom in data))
return "Aucun chat s'appelant "
+
nom +
" n'a été trouvé."
;
var chat =
data[
nom];
var message =
nom +
", est né le "
+
formatDate
(
chat.
naissance) +
" de la mère "
+
chat.
mere;
if (
"deces"
in chat)
message +=
", décédé le "
+
formatDate
(
chat.
deces);
return message +
"."
;
}
print
(
renseignementSurChat
(
tousLesChats,
"Gros Igor"
));
La première instruction return dans renseignementSurChat est utilisée comme issue de secours. Si aucune donnée n'est fournie sur un chat particulier, le reste de la fonction est dépourvu de sens, nous renvoyons donc immédiatement une valeur qui empêche le reste du code de s'exécuter.
Dans le passé, certains groupes de programmeurs considéraient comme malsaines les fonctions contenant de multiples instructions return. Selon eux, cela rendait difficile de voir quel code était exécuté et quel code ne l'était pas. D'autres techniques, qui seront abordées dans le chapitre 5, ont rendu cet argument plus ou moins obsolète, mais vous pouvez toujours tomber à l'occasion sur quelqu'un qui critiquera l'utilisation de raccourcis avec l'instruction return.
La fonction formatDate utilisée par renseignementSurChat n'ajoute pas de zéro avant la partie mois et jour quand ce sont des nombres à un seul chiffre. Écrivez une nouvelle version qui fera cela.
function formatDate
(
date) {
function pad
(
number) {
if (
number <
10
)
return "0"
+
number;
else
return number;
}
return pad
(
date.getDate
(
)) +
"/"
+
pad
(
date.getMonth
(
) +
1
) +
"/"
+
date.getFullYear
(
);
}
print
(
formatDate
(
new Date(
2000
,
0
,
1
)));
Écrivez une fonction lePlusVieuxChat qui, étant donné un objet ayant des chats comme arguments, renvoie le nom du plus vieux chat vivant.
function lePlusVieuxChat
(
data) {
var lePlusVieux =
null;
for (
var nom in data) {
var chat =
data[
nom];
if (!(
"deces"
in chat) &&
(
lePlusVieux ==
null ||
lePlusVieux.
naissance >
chat.
naissance))
lePlusVieux =
chat;
}
if (
lePlusVieux ==
null)
return null;
else
return lePlusVieux.
nom;
}
print
(
lePlusVieuxChat
(
tousLesChats));
La condition donnée avec la commande if pourrait paraître un peu intimidante. On peut la lire comme : « ne stocker le chat en cours dans la variable lePlusVieux que s'il n'est pas mort, et si lePlusVieux est soit null soit un chat qui est né après le chat en cours ».
Notez que cette fonction renvoie null quand il n'existe aucun chat vivant dans data. Que devient notre solution dans cas ?
Maintenant que vous êtes familiarisé avec les tableaux, je peux vous montrer quelque chose de lié. Quel que soit le nom d'une fonction, une variable spéciale nommée arguments est ajoutée à l'environnement dans lequel le corps de la fonction tourne. Cette variable se réfère à un objet qui ressemble à un tableau. Il a la propriété 0 pour le premier argument, 1 pour le second, et ainsi de suite pour chaque argument donné par la fonction. Il possède également une propriété length.
Cependant, cet objet n'est pas véritablement un tableau, il ne possède pas de méthodes telles que push et il ne met pas à jour automatiquement sa propriété length quand vous lui ajoutez quelque chose. Pourquoi n'est-ce pas le cas ? Je n'ai jamais vraiment compris l'utilité de tout cela, mais c'est quelque chose dont vous devez avoir connaissance.
function compteurArgument
(
) {
print
(
"Vous m'avez donné "
,
arguments.
length,
" arguments."
);
}
compteurArgument
(
"Mort"
,
"Famine"
,
"Fléau"
);
Certaines fonctions peuvent prendre un certain nombre d'arguments, par exemple la fonction print. Cette fonction particulière opère une boucle sur les valeurs des arguments d'un objet pour en faire quelque chose. Les autres peuvent prendre des arguments de manière optionnelle qui sont initialisés à une valeur par défaut sensée si l'utilisateur ne fournit pas de valeur.
function ajouter
(
nombre,
combien) {
if (
arguments.
length <
2
)
combien =
1
;
return nombre +
combien ;
}
show
(
ajouter
(
6
));
show
(
ajouter
(
6
,
4
));
Étendez la fonction serie de l'exercice 4.2 pour prendre un second argument, optionnel. Si un seul argument est donné à la fonction, elle se comporte comme précédemment et produit une série commençant à 0 jusqu'au nombre donné. Si deux arguments sont donnés, le premier indique le début de la série, le second la fin.
function serie
(
debut,
fin) {
if (
arguments.
length <
2
) {
fin =
debut;
debut =
0
;
}
var resultat =
[];
for (
var i =
debut;
i <=
fin;
i++
)
resultat.push
(
i);
return resultat;
}
show
(
serie
(
4
));
show
(
serie
(
2
,
4
));
L'argument optionnel ne fonctionne pas exactement comme le premier, dans l'exemple ajouté ci-dessus. Quand il n'est pas précisé, le premier argument prend le rôle de fin et debut devient 0.
Vous devez vous rappeler de la ligne de code cité en introduction :
print
(
somme
(
serie
(
1
,
10
)));
Nous avons la fonction serie maintenant. Tout ce dont nous avons besoin pour faire fonctionner cette ligne est une fonction somme. Cette fonction prend un tableau de nombres en arguments et retourne leur somme. Écrivez-la, ce devrait être simple.
function somme
(
nombres) {
var total =
0
;
for (
var i =
0
;
i <
nombres.
length;
i++
)
total +=
nombres[
i];
return total;
}
print
(
somme
(
serie
(
1
,
10
)));
Le chapitre précédent nous a permis d'étudier les fonctions Math.max et Math.min. Avec ce que vous connaissez maintenant, vous pourrez noter que max et min sont déjà les propriétés d'un objet enregistré sous le nom de Math. Voici un autre rôle que les objets peuvent jouer : celui d'entrepôt pour un grand nombre de valeurs liées.
Il y a beaucoup de valeurs dans Math, si elles avaient été placées directement dans l'environnement global, elles l'auraient, comme on dit, pollué. Plus il y a de noms utilisés, plus il est probable d'écraser par accident la valeur d'une variable. Par exemple, il n'est pas incongru de vouloir nommer une variable max.
La plupart des langages vous arrêteront, ou du moins vous alerteront, quand vous définirez une variable avec un nom déjà utilisé par l'environnement. Pas JavaScript.
Dans tous les cas, on peut trouver tout un ensemble de fonctions mathématiques et de constantes dans Math. Toutes les fonctions trigonométriques sont présentes : cos, sin, tan, acos, asin et atan. π et e, qui sont écrits en capitales (PI et E), ce qui était à une époque une façon très à la mode d'indiquer que quelque chose est une constante. pow est un bon moyen de substitution des fonctions puissance que nous avons écrites, il accepte les exposants négatifs et fractionnaires. sqrt extrait la racine carrée d'un nombre. max et min peuvent donner le maximum ou le minimum de deux valeurs. round, floor, et ceil vont respectivement arrondir un nombre à l'entier le plus proche, à l'entier inférieur et supérieur le plus proche.
Il existe un grand nombre d'autres valeurs dans Math, mais ce texte est une introduction, pas une référence. Les références sont ce que vous consultez lorsque vous soupçonnez qu'il existe quelque chose dans un langage, mais avez besoin de savoir comment ça s'appelle ou comment ça marche au juste. Malheureusement, il n'existe aucune référence totalement exhaustive pour le JavaScript. C'est essentiellement parce que sa forme courante est la résultante d'un processus chaotique pendant lequel différents navigateurs lui ont ajouté diverses extensions à différentes périodes. Le document standard ECMA, mentionné dans l'introduction, fournit une solide documentation du langage de base, mais il est plus ou moins lisible. Pour la plupart de vos questions, vous pouvez compter sur le Mozilla Developer Network.
Vous avez peut-être déjà pensé à un moyen de découvrir ce qui est disponible avec l'objet Math :
Mais hélas, rien n'apparaît. De même, quand vous faites ceci :
Vous ne voyez que 0, 1, et 2, pas length, ni push, ou join, qui s'y trouvent pourtant bel et bien. Apparemment, certaines propriétés des objets sont cachées. Il y a une bonne raison à ça : tous les objets ont quelques méthodes, par exemple toString qui convertit l'objet en une sorte de chaîne pertinente, mais vous ne souhaiterez sûrement pas les voir quand vous êtes par exemple, à la recherche des chats que vous avez stockés dans l'objet.
Pourquoi les propriétés de Math sont-elles cachées ? Ce n'est pas très clair pour moi. Il y a sûrement quelqu'un qui a voulu en faire un type d'objet mystérieux.
Toutes les propriétés que vos programmes ajoutent aux objets sont visibles. Il n'y a pas moyen de les cacher, ce qui est regrettable parce que, comme vous le verrez dans le chapitre 8, il serait sympa d'ajouter des méthodes aux objets sans avoir à les rendre visibles dans des boucles for/in.
Certaines propriétés sont en lecture seule, vous pouvez récupérer leur valeur mais pas la modifier. Par exemple, les propriétés d'une valeur de chaîne sont toutes en lecture seule.
var tableau =
[
"Ciel"
,
"Terre"
,
"Homme"
];
tableau.
length =
2
;
show
(
tableau);