Makina Blog

Le blog Makina-corpus

Bien démarrer avec JavaScript


Callbacks, this, scope… mais qu'est-ce que c'est que ce langage ?

JavaScript a mauvaise presse auprès des développeurs des autres langages. J'ai vu de nombreux développeurs râler quand ils ont dû en écrire quelques instructions. Il faut bien avouer que certains comportements ne sont pas intuitifs (merci à Gary Bernhardt). Cependant, le constat est clair : lire la doc du langage permet de comprendre bien des choses dans ce langage qui anime toutes nos pages web.

Les deux mondes

Le premier des deux mondes apparut en 1995 grâce à Brendan Eich, qui travaillait chez Netscape. À cette période et jusqu'à maintenant, JavaScript sert à animer des pages web en proposant des API et la gestion de l'arbre DOM dans le navigateur du visiteur.

Le deuxième monde est apparu en 2009 grâce à Ryan Dahl qui se dit alors que le moteur V8 de Google est suffisamment rapide pour faire tourner du code côté serveur. Il exploite donc le moteur et y ajoute quelques API, notamment une API d'entrée/sortie non bloquantes et des modules.

Depuis 2009, JavaScript est donc un langage permettant d'exécuter des instructions côté client dans le navigateur du visiteur, ou côté serveur dans l'environnement du fournisseur de services. Ces deux mondes sont très proches, mais ont leurs particularités comme nous le verrons dans la suite.

This and the scope

Un des pièges couramment rencontrés par les développeurs qui veulent écrire du JavaScript sans en comprendre les concepts provient du scope. Le scope est ce qu'on appelle en français la portée, c'est-à-dire la visibilité accordée à une variable. Le scope porte également le contexte dans lequel les instructions vont s'exécuter.

Le scope global

Dans les deux mondes, le scope global est différent. Mais le concept est le même. Si une déclaration est globale, sa valeur sera accessible partout. Pour le navigateur, le scope global est l'objet window. En fait, si nous chargeons un fichier JavaScript dans une page html avec la seule déclaration suivante :

var sideEffect = "Hello World!";

function runInLocalScope() {
  console.log(sideEffect); //Hello World!
}

runInLocalScope();

Tout se passe comme si on écrivait ceci :

window.sideEffect = "Hello World!";

window.runInLocalScope = function() {
  console.log(window.sideEffect);
};

window.runInLocalScope(); //Hello World!

// d'ailleurs

console.log(window.runInLocalScope); //function()...

Nous voyons tout de suite ce qui peut poser souci, j'ai laissé un indice en nommant notre variable :

var sideEffect = "Hello World!";

function runInLocalScope() {
  console.log(sideEffect);
}

function doSideEffect() {
  sideEffect = "Farewell and good night!";
}

runInLocalScope(); //Hello World!
doSideEffect();
runInLocalScope();//Farewell and good night!

Notre variable sideEffect n'est pas immutable, la fonction doSideEffect en a modifié son contenu, pour l'ensemble du scope global et donc quel que soit l'endroit de mon application dans lequel je vais appeler cette variable.

Il est donc recommandé de définir ses variables et ses fonctions dans des scopes locaux, afin d'eviter les effets de bord ce qui limitera les bugs. Quelques patterns viennent à votre rescousse, comme la fonction anonyme auto-appelante (IIFE, Immediately-Invoked Function Expression), comme par exemple :

var noSideEffect = "Hello World!";

function runInGlobalScope() {
  console.log(noSideEffect);
}

(function() {
  var noSideEffect = "Farewell and good night!";

  function runInLocalScope() {
    console.log(noSideEffect);
  }

  runInLocalScope(); //Farewell and good night!
  runInGlobalScope(); //Hello World!
})();

runInGlobalScope(); //Hello World!

Le scope local

En JavaScript, il n'existe que deux portées : le scope global et le scope local. Pour en faire le tour, voyons ensemble un effet amusant pour comprendre le scope local. Quel sera l'affichage dans la console de cette suite d'instructions ?

for(var index = 0 ; index < 5 ; index++) {
  setTimeout(function() {
    console.log("with side effect", index);
  }, 1000);
}

Comparons ce code à la version ci-dessous :

function recreateLocalScope(localIndex) {
  return function() {
      console.log("no side effect", localIndex);
  };
}

for(var index = 0 ; index < 5 ; index++) {
  setTimeout(recreateLocalScope(index), 1000);
}

Même si nous nous trompons tous en essayant d'anticiper le résultat de ces instrutions, en faisant tourner ce code, on comprend vite ce qu'il se passe au sein des variables qui ne sont pas définies clairement dans un scope local.

This

Dans de nombreux langages, this est un pointeur vers l'objet courant. C'est un peu différent en JavaScript ; this est un pointeur vers le contexte qui exécute les instructions. Comme le rappelle wikipedia, et de manière pragmatique, this peut se référer à :

  • l'objet window s'il est utilisé dans un contexte de scope global ;
  • l'objet portant la fonction qui est appelée, correspondant donc à un scope local.

Mais this dépend du contexte dans lequel la fonction est appelée. Si la fonction est appelée via un événement comme le click de la souris, alors this correspondra à ce contexte-ci, comme nous pouvons le voir ici :

function displayThis() {
  console.log(this);
}

displayThis(); //[object window]

document.getElementById("button").addEventListener("click", displayThis); //[object HTMLButtonElement]

Il existe même deux fonctions pour changer le contexte de l'appel d'une fonction. bind et call. N'hésitez pas à vous référer aux exemples de MDN (Mozilla Developer Network, la bible de la documentation JavaScript) pour voir comment utiliser ces deux fonctions.

Asynchrone et l'enfer du callback

Le deuxième piège souvent rencontré par les développeurs vient de la capacité de JavaScript a être un langage fonctionnel. C'est à dire que les fonctions sont des valeurs comme les autres :

var funfunvariable = function(name) {
  console.log("Hello " + name + "!");
}

function funfunfunction(name) {
  console.log("Hello " + name + "!");
}

var funfun = funfunfunction;

La seule différence entre ces deux déclarations est dans l'ordre d'évaluation de mise à disposition des fonctions dans le scope. L'instruction function permet à celle-ci d'être appelée avant sa déclaration. Alors que l'instruction sous forme de variable doit être interprétée avant tout appel à cette variable.

Une fois que nous avons compris cela, nous pouvons commencer à comprendre les callbacks. Que se passerait-il si je passais funfunvariable en tant que paramètre d'une autre fonction ?

function add(number1, number2) {
  return number1 + number2;
}

function multiply(number1, number2) {
  return number1 * number2;
}

function calculate(operation, number1, number2) {
  return operation(number1, number2);
}

console.log(calculate(add, 5, 7));
console.log(calculate(multiply, 5, 7));

En faisant la synthèse de tout ce que nous avons vu précédemment, on commence à entrevoir les points sur lesquels notre attention devra se porter dans notre code JavaScript. Si nous passons une fonction en paramètre d'une autre fonction, dans quel scope nous trouvons-nous ? Mais alors, que vaut this ?

var numbers = [5, 7];

function calculate(operation) {
  return operation(this.numbers[0], this.numbers[1]);
}

function add(number1, number2) {
  return number1 + number2;
}

console.log(calculate(add)); //12

(function() {
  var localScope = {
    numbers: [15, 21]
  };
  console.log(calculate.call(localScope, add)); //36
})();

Dans cet exemple, nous voyons comment modifier le contexte de la fonction calculate avec la fonction call. Nous avons l'utilisation d'une fonction callback dans le paramètre de la fonction calculate. Et nous avons une utilisation de this qui peut être troublante.

Dans la vraie vie, les exemples d'utilisation courante que nous rencontrons pour les callbacks concernent plus volontiers les appels asynchrones, comme les appels HTTP à des ressources distantes. Autrement dit, nous passons à la fonction d'appel du json une fonction qui s'exécutera avec le json lorsque celui-ci sera renvoyé par le serveur :

function callGithubRepo(callback) {
  $.ajax({
    dataType: "json",
    url: 'https://api.github.com/repos/jquery/jquery/issues/1',
    success: callback
  });
}

function onSuccess(data) {
  console.log("The first issue on jquery is from: " + data.user.login);
  console.log("and its state is: " + data.state);
}

console.log("We will call the github API for the jquery repo...");
callGithubRepo(onSuccess);
console.log("The call is asynchronous...");

Dans cet exemple, nous appelons l'API de GitHub pour afficher la première issue sur le repository de jQuery grâce à un appel AJAX fait à l'aide de jQuery. Avant et après l'instruction qui va faire l'appel, nous affichons un message sur la console et nous verrons que le résultat de ces instructions affichent quelque chose comme ça :

"We will call the github API for the jquery repo..."
"The call is asynchronous..."
"The first issue on jquery is from: rkatic"
"and its state is: closed"

La fonction callGithubRepo prend un callback en paramètre et implémente l'appel avec jQuery. On aurait très bien pu l'écrire sans jQuery :

function callGithubRepo(callback) {
  var httpRequest = new XMLHttpRequest();

  httpRequest.onreadystatechange = function() {
    if (httpRequest.readyState === XMLHttpRequest.DONE) {
      if (httpRequest.status === 200) {
        callback(JSON.parse(httpRequest.responseText));
      }
    }
  };

  httpRequest.open('GET', 'https://api.github.com/repos/jquery/jquery/issues/1');
  httpRequest.send();
}

Et cætera

Le prochain JavaScript

Vous le savez peut-être, JavaScript évolue. Sa syntaxe évolue, ainsi que quelques enrichissements de fonctions et API.

Si vous voulez en savoir plus, Babel fait la liste de toutes les nouveautés que vous pouvez d'ores et déjà adopter grâce à Babel lui-même.

Pour revenir sur les points que nous avons vu, commencez donc par étudier les promesses, qui viennent répondre au même besoin que les callbacks en paramètres de fonction pour gérer les appels asynchrones de manière plus explicite, avec par exemple :

callGithubRepo().then(onSuccess);

Vous pouvez également découvrir les arrow functions qui viennent supplanter les fonctions anonymes avec une gestion de this plus facile à deviner.

Lire

De nombreux ouvrages pour découvrir ou approfondir son usage de JavaScript sont disponibles en lignes. Je pense notamment à celui d'Axel Rauschmayer, speaking JavaScript, ou celui d'Addy Osmani, JavaScript Design Patterns ou encore Eloquent JavaScript de Marijn Haverbeke.

Ils sont tous référencés sur la page opensource JSbooks.

Si vous ne savez pas par où commencer, lisez donc cet article de Douglas Crockford : JavaScript:
The World's Most Misunderstood Programming Language
.

Awesome JavaScript

Toujours sur GitHub et parce que la communauté JavaScript est immense, n'hésitez pas à fouiller dans awesome-JavaScript, une collection de bibliothèques, ressources et bonnes pratiques autour de JavaScript.

Outils

J'ai utilisé JS Bin pour proposer des exemples de code qui tournent pour de vrai. Il y en a des tas comme JSFiddle, CodePen. Ils permettent même de charger des bibliothèques extérieures comme les frameworks à la mode ou jQuery.

Apprenez à utiliser les devtools de votre navigateur, que ce soit celui de Google ou celui de Mozilla ou celui de Microsoft.

One more thing: use strict

Vous voilà armé pour (re)commencer votre découverte de JavaScript. Si je peux vous recommander une dernière chose, utilisez toujours le mode strict dans des fonctions IIFE pour clore la portée de vos déclarations. Ce mode, déclenché par une instruction particulière, permet de choisir une variable restrictive de JavaScript :

(function() {
  'use strict';

  // un monde plus sûr

})();

Il va éliminer quelques erreurs silencieuses en versions explicites avec une exception qui sera levée. Il va aussi permettre aux moteurs d'effectuer des optimisations en corrigeant des erreurs, le code sera exécuté plus rapidement. Enfin il va interdire les mots-clés susceptibles d'être définis dans les futures versions de ECMAScript.

Formations associées

Formations Front end

Formation Développement d'applications JavaScript

Toulouse Du 18 au 20 juin 2024

Voir la formation

Actualités en lien

Image
Capture d'une partie de carte montrant un réseau de voies sur un fond de carte sombre. Au centre, une popup affiche les information de l'un des tronçons du réseau.
28/02/2024

Géné­rer un fichier PMTiles avec Tippe­ca­noe

Exemple de géné­ra­tion et d’af­fi­chage d’un jeu de tuiles vecto­rielles en PMTiles à partir de données publiques.

Voir l'article
Image
Read The Docs
01/02/2024

Publier une documentation VitePress sur Read The Docs

À l'origine, le site de documentation Read The Docs n'acceptait que les documentations Sphinx ou MKDocs. Depuis peu, le site laisse les mains libres pour builder sa documentation avec l'outil de son choix. Voici un exemple avec VitePress.

Voir l'article
Image
Widget
04/04/2023

Créer une application en tant que composant web avec Stencil

Mise en place dans le cadre de Geotrek, cette solution permet de se passer d'une iFrame pour afficher une application dans n'importe quel site.

Voir l'article

Inscription à la newsletter

Nous vous avons convaincus