JavaScript, les bons éléments, façon 2020 : Introduction

ou comment JavaScript est devenu un vrai langage

Il existe un livre nommé JavaScript, the good parts publié en 2008 qui relate tous les éléments à retenir d’un langage qui a été, et est toujours fortement décrié parmi l’univers des développeurs.

De la syntaxe, des fonctions en passant par les opérateurs, toutes les bonnes idées d’un langage qui dans la nature s’approche plus du Lisp que du Java y sont passées.

Sans avoir la prétention d’écrire un livre (et encore moins aussi bon que celui-ci) explicitant les bons points de JavaScript, il va suivre une série d’articles qui seront consacrés à énumérer des choses qui ont changé (en bien) ces dernières années. Le langage a très fortement évolué depuis 2008, avec des nouveautés syntaxiques comme fonctionnelles et il continue encore fortement d’avancer aujourd’hui via un système ouvert de soumissions.

L’état de JavaScript en 2008

Afin d’introduire la série de documents qui va suivre, il est nécessaire de faire un état rapide de ce qu’était le langage à l’époque, avant même l’ES5.

Au cours de cette série d’articles, aucune nouveauté ne sera utilisée dans les exemples de code avant d’être explicitement présentée.

Syntaxe, variables et types

JavaScript se rapproche de la syntaxe du C, tout en étant un langage typé dynamiquement (les types ne sont jamais précisés explicitement).

Les points virgules sont facultatifs, mais conseillés dans certains cas en raison du parsage… disons étonnant de certaines structures.

// Déclaration d'une variable 'counter' avec la valeur 3
var counter = 3;

// On peut la réutiliser plus tard
console.log(counter + 2); // 5

// ...et la réassigner (sans var)
counter = 5;

// Déclaration d'un objet vide 
var container = {};
// Ajout d'une propriété de type string
container.hello = 'world';

Différents types sont présents par défaut :

  • number, un type universel pour les flottants et entiers (en interne, c’est un flottant 64 bits, dit généralement double).
  • string, un type qui symbolise une chaîne de caractères. Il n’y a ni type char (caractère seul), ni type byte (un octet). Généralement, tout est une string. Celles-ci sont encodées en interne en UTF-16 (un choix discuté). Les chaînes sont immuables et la taille d’une chaîne est stockée dans .length.
  • boolean, valant true ou false.
  • object, type étant en réalité un ensemble de couples clé => valeur, avec une clé de type string.
  • function, représentant un object pouvant être appelé.

Et deux types "sans valeur" utilisés dans des contextes différents :

  • null
  • undefined

Les tableaux sont des objets spéciaux, les indexs sont des propriétés, et chacun contient une propriété exotique .length.

Lorsque des variables sont déclarées avec var, elles sont locales à une fonction et non pas au bloc. Ceci rappelle le scoping mis en place en Python ou en PHP.

Il est possible de "déclarer" sans utiliser var, cela étant en fait une écriture dans l’objet global, sorte de "conteneur" de votre script. Dans le navigateur, cet objet est window.

window.document.getElementById()
// même chose qu'écrire
document.getElementById()

Structures de contrôle et opérateurs

Un bloc est contrôlé par les caractères {} (comme les objets, oui).

Les classiques if, else if, else, while et for (du C) sont disponibles. Pour comparer, on utilisera les <, >, <=, >=, === et !==.

== et != existent mais peu recommandés (ils font de l’inférence de type pour comparer).

if (counter > 3) {
  console.log('counter est supérieur à 3 !');
}

var i;
for (i = 0; i < 10; i++) {
  console.log(i);
}

On rappelle que var déclare localement à une fonction : déclarer dans une structure de contrôle ne rend pas cette variable locale au bloc !

// on peut tester la truthiness d'une variable: 
// pour un nombre, c'est number !== 0
if (counter) {
  var local_variable = 3;
}

// La variable est déclarée, ici
console.log(local_variable); // 3

Pour déclarer, ajouter des données et itérer sur des tableaux, cela se passe comme ceci :

// Les types peuvent être mixés dans les tableaux
var arr = ['0', 1, 2, 3];

// Ajoute 4 à la fin du tableau
arr.push(4);

var i;
for (i = 0; i < arr.length; i++) {
  // L'index des tableaux débute à 0
  console.log(arr[i]);
}

Pour itérer sur les clés d’un objet (propriétés), on peut utiliser for in. Celui-ci s’accompagne d’effet de bord sur les propriétés dans la chaîne de prototype (un type d’héritage propre à JS).

var key;
for (key in container) {
  console.log(key + ' : ' + container[key]);
}

Fonctions

Pour déclarer une fonction, on passe par le mot clé function. Il y a deux manières complètement équivalentes de le faire.

Les fonctions sont des objets en JavaScript, elles sont donc stockables dans des variables et peuvent être passées en paramètre d’autres fonctions.

// Déclaration
function hello(a, b) {

}

// ou
// Affectation
var hello = function (a, b) {

};

Les paramètres sont tous facultatifs, si un paramètre n’est pas spécifié (ou si, au contraire, plus de paramètres que prévu l’ont été), aucune erreur n’est lancée.

Pour vérifier qu’un paramètre a été passé, on peut tester si il est undefined.

function hello(a, b) {
  if (a === undefined) {
    throw new Error('a is required');
  }

  // Une variable spéciale 'arguments' existe pour
  // récuperer les paramètres variables
  var i;
  for (i = 0; i < arguments.length; i++) {
    console.log('argument ' + i + ' : ' + arguments[i]);
  }
}

// Valide
hello(1);

// Aussi valide
hello(1, 2, 3);

Instances d’objets

Il est possible de créer des instances d’objets partageant des méthodes et propriétés identiques.Pour cela, on déclare un constructeur qui sera une fonction, et on définit des propriétés dans sa chaîne de prototype (héritée par toutes les instances créées).

// Par convention, les constructeurs commencent par une majuscule
function User(id, name) {
  // this réfère au nouvel objet créé
  this.id = id;
  this.name = name;
}

User.prototype.getName = function() {
  // this réfère à l'instance portant la méthode
  return this.name;
};

// On invoque un constructeur avec new
var jimmy = new User(1, 'Jimmy');

console.log(jimmy.getName()); // 'Jimmy'

Modules et séparation des fichiers

JavaScript ne disposait pas de modules : les fichiers JavaScript écrivaient tous dans un objet global leurs fonctions et variables, et tout le monde pouvait voir les données de tout le monde. Si cela est relativement commun dans beaucoup de langages (comme C ou PHP), les problèmes de collision de noms sont gérés par des namespace, sorte de préfixe à un groupe de fonctions et variables. Les namespaces sont généralement cumulables, il peut y avoir des namespaces dans d’autres.

En JS, point d’espaces de noms, point de mot clé static pour limiter des fonctions à un seul fichier, le problème est même "pire" : n’importe quelle variable déclarée avec var pouvait être redéclarée silencieusement avec une valeur écrasant la précédente. Si une function était déclarée avec le même nom dans deux fichiers, alors celle lu en dernier allait écraser les autres portant le même nom.

Autre bizarrerie, lié au fait que JavaScript a été pensé pour être utilisé dans un navigateur web : Il n’y avait pas de système d’inclusions de fichiers, aussi rudimentaire qu’il soit. Pour inclure des scripts, cela se passait en dehors de ceux-ci, en HTML, où des balises <script> importaient des fichiers dans l’ordre que le programmeur avait décidé.

I/O

En 2008, il est purement impossible de lire un fichier depuis le navigateur en JavaScript de manière standard, même des fichiers dans des input :). L’API FileReader est arrivée en 2009-2010, et n’est pas supportée avant IE 10 (2012).

Pour écrire des fichiers, JavaScript en est incapable, de manière volontaire : le navigateur n’a pas à accéder au disque dur de l’ordinateur par mesure de sécurité.

Pour le web, JavaScript est capable d’envoyer des requêtes HTTP à l’aide du mal nommé XMLHttpRequest, qui a une API un poil moisie (il y a certes pire ailleurs, hum urllib hum).

const req = new XMLHttpRequest();

req.open('GET', 'https://google.com', true);

// Cette fonction va s'exécuter quand le chargement est terminé
req.onload = function () {
  console.log('response: ' + req.responseText);
};
// Celle-ci si une erreur est rencontrée
req.onerror = function (evt) {
  console.log('error: ' + evt);
};

req.send();

La fin

Ainsi se conclut notre petite introduction au vieux JavaScript. La prochaine partie parle des modules ECMAScript et du CommonJS, juste ici. 😀

Article suivant : Modules

Laisser un commentaire