Sur l’évolution de JavaScript

Dans cet article, je souhaite simplement évoquer les quelques moments où j’ai découvert qu’il était possible de réaliser, à partir de JavaScript pur et simple, ce que moi, et probablement d’autres développeurs, ont pris pour habitude de faire à l’aide de frameworks ou librairies.

Apparu tout d’abord dans Netscape 2.0 en 1996, JavaScript, conçu par Brendan Eich, est né de l’idée qu’une part de la logique de traitement pourrait être déportée coté client, en apportant plus d’interaction avec l’utilisateur. Le langage s’est ensuite retrouvé embarqué dans Internet Explorer 3 sous le nom de JScript.

Dans l’idée de faire en sorte que tout les navigateurs implémentent des versions aussi cohérentes que possible des moteurs JavaScript, ce dernier se voit rapidement régi par un standard : la norme ECMAScript (développée par l’organisme ECMA). La première version de cette norme a vu le jour en 1997, et la dernière en date (version 10) date de Juin 2019. JScript comme JavaScript (ou bien encore ActionScript) ne sont que différentes implémentations de cette norme.

En parallèle, HTML et CSS ont évolué, beaucoup plus ponctuellement, et il me parait important de parler ici de la version 5 de HTML. En 2014, HTML 5 a probablement marqué un tournant significatif, entre autres apports, en étendant les possibilités d’interactions offertes par JavaScript avec l’environnement proche du navigateur web au travers d’un ensemble d’APIs (fonctionnalités de géolocalisation, stockage de données, par exemple). Cette évolution a, de fait, rendu possible l’implémentation de nombreuses fonctionnalités sur les sites web, pour le meilleur, ou pour le pire parfois. Je ne peux, au passage, que conseiller l’excellente documentation disponible sur le site de Mozilla.

Par ailleurs, et comme c’est le cas pour beaucoup de langages de développement, quantité de frameworks et librairies sont progressivement apparus pour former un écosystème JavaScript. Comme dans tout les langages de développement, un framework ou une librairie a pour objectif de permettre une utilisation du langage dans un domaine particulier (la cartographie, par exemple, avec Leaflet). Ces outils peuvent aussi, plus globalement, favoriser une utilisation particulière du langage, un style de programmation, en proposant une « surcouche » dudit langage. Dans le cas de JavaScript, on trouve également des « polyfills », dont le but est d’uniformiser certaines fonctionnalités (présentes nativement dans la norme, parfois) d’un navigateur à l’autre. Parmi les librairies les plus utilisées, citons bien entendu le très populaire jQuery, dont la première version a vu le jour en 2006, et qui a, pendant de nombreuses années, constitué un outil quasi incontournable en développement front-end.
On peut résumer le principe de cette librairie de façon suivante :

  • Désigner un ou plusieurs élément(s) d’une page HTML. L’idée centrale a consisté à baser la méthode de sélection de la collection d’éléments sur le chemin CSS qui permet de qualifier ces éléments.
  • Assigner à ces éléments un comportement spécifique en réaction à des événements (par exemple : afficher une popup de confirmation lorsque je clique sur le bouton « Confirmer »).

Vous avez probablement entendu parler de Vanilla JS. Simulant la présentation d’un framework ultra performant et léger, il moque en réalité l’utilisation abusive de ces frameworks (jQuery et ses nombreux plugins en tête de liste), arguant que tout ce qu’ils permettent d’effectuer, soit-disant, peut se faire avec du JS pur et simple (c’est ce que les auteurs appellent « Vanilla JS »). On notera au passage que les articles sur Vanilla JS sont très souvent le lieu de nombreux échanges enflammés. Attardons nous sur l’un des exemples fréquemment cités pour illustrer en quoi l’utilisation de jQuery serait superflue : il s’agit du principe de sélecteurs pour désigner un ou plusieurs éléments du DOM. Avec jQuery, vous sélectionnez un ou plusieurs éléments de la page de la façon suivante :

$('div .class')

Rien de bien plus compliqué, à présent, en JavaScript natif :

document.querySelectorAll('div .class')

Pourquoi se casser la tête, n’est-ce pas ? Si, depuis quelques années maintenant, on peut légitimement se poser la question (voire même carrément y répondre) de l’intérêt de charger jQuery sur une page web lorsqu’on le cantonne à cette utilisation, il ne faut pas perdre de mémoire que cette comparaison ne tenait pas la route lors des premières versions de jQuery. On ne pouvait tout simplement pas faire cela en JavaScript natif (sur aucun navigateur). C’est lors de l’apparition de l’API Selectors, et plus précisément lorsque le niveau de support de cette API a commencé à être implémentée par un nombre suffisant de navigateurs, que la comparaison a pu paraître sensée. En fait, c’est bien jQuery et son utilisation très répandue qui a été une source d’inspiration pour élaborer cette API, comme me l’a confirmé Lachlan Hunt, éditeur principal de cette spécification, à qui j’ai posé la question (au risque d’enfoncer les portes ouvertes).
Voici sa réponse :

As part of the standardisation process, it’s always important to look at and understand what developers are currently using, and jQuery was certainly looked at in detail during the spec’s development. We tried to minimise the behavioural differences between querySelectorAll() and $() where possible, while working within the constraints of the existing web platform.

While I also can’t be 100% certain that jQuery was the only inspiration for it, I’m sure it did play a big part in it gaining traction, and I can tell you that some of jQuery’s behaviour that allowed selectors to begin with combinators was the inspiration for the addition of the :scope pseudo-class, and the planned (but subsequently dropped) feature to allow relative selectors in Selectors API level 2.

On peut comprendre de cette réponse que les principales fonctionnalités de l’API Selectors ont été inspirée d’une librairie (certes, non des moindres), et plus particulièrement de son utilisation très largement répandue.

Cette impression de voir un principe de conception mis en œuvre dans un premier temps dans un framework, pour ensuite être repris dans les spécifications même de JavaScript, je l’ai pour ma part à nouveau vécue après avoir découvert et utilisé quelques temps AngularJS. Avec ce framework est apparue la possibilité, entre autre, d’utiliser une architecture MVC en développement front-end, ce qui constituait jusqu’alors un manque certain. MVC, c’est la décomposition du code d’une application en trois types de responsabilités différentes :

  • M pour Modèle : comment sont structurées les données, comment elles doivent évoluer, rester cohérentes, etc. AngularJS permet de regrouper ces éléments de logique métier dans des services, dont on peut disposer (que ce soit dans des contrôleurs ou d’autres services) grâce à un mécanisme d’injection de dépendance.
  • V pour Vue : on s’intéresse ici à tout ce qui va permettre à l’utilisateur d’avoir une représentation des données et un mode d’interaction avec celles-ci. AngularJS propose notamment un système de templates pour gérer cet aspect.
  • C pour Contrôleur : qui regroupe les parties du codes qui gèrent la cohérence et les interactions entre le modèle et la vue.

En particulier, AngularJS offre la possibilité de définir des éléments HTML personnalisés (ici my-component par exemple):

angular.module('myApp').component('myComponent', {
  templateUrl: 'myComponent.html',
  bindings: {
    myVar: '='
  }
});

On peut ensuite utiliser dans son application

<div>
    <my-component>
        ...
    </my-component>
</div>

et leur appliquer des traitements de façon relativement isolée, en y associant des contrôleurs.

Après avoir développé et notamment utilisé les directives et components d’AngularJS, j’ai découvert la spécification des Web Components, et plus précisément les Custom Elements. La spécification des Web Components regroupe en réalité un ensemble d’outils permettant de concevoir des éléments graphiques d’une interface web, personnalisés et réutilisables. Dans cette boîte à outils, on trouve donc plus particulièrement la spécification des Custom Elements. Il s’agit ici de définir un élément HTML spécifique, afin de pouvoir y attacher des comportements qui lui sont propres. Sous Firefox par exemple, on peut déclarer un tel élément de la façon suivante :

window.customElements.define('my-custom-element', 'CustomElementClass')

pour ensuite y associer une classe JavaScript (ECMAS2015) :

class CustomEltClass extends HTMLParagraphElement {
    ...
}

Avant de me documenter pour la rédaction de cet article, je m’étais mis en tête que, comme pour les jQuery et l’API Selectors, c’était le principe des directives d’AngularJS, et les notions similaires dans les autres frameworks, qui avaient été une source d’inspiration pour l’élaboration de cette norme. Pour vérifier cette intuition, je me suis permis de contacter le dernier éditeur en date de la norme Custom Elements (qui est aujourd’hui obsolète au profit de celle des Web Components), pour lui poser cette question mais lui ne semble pas du tout soutenir cette vision des choses, selon laquelle ce serait un framework (Angular, ou bien d’autres qui utilisent également ce principe de définition d’éléments HTML personnalisés, comme React par exemple).
La chronologie des événements vient en fait lui donner raison puisque, en tout cas pour AngularJS, la rédaction de la norme des Custom Elements semble avoir démarré au minimum à la même période où apparaissaient la notion de directive dans AngularJS. S’il semble que la mise en pratique de ce principe dans de telles librairies devance son utilisabilité « native » dans JavaScript (il est important de remarquer que les Web Components ont encore aujourd’hui recours à des polyfills sur bon nombre de navigateurs), la spécification préalable à cette utilisabilité a donc commencé sans que l’inspiration provienne d’un de ces frameworks.

Quoiqu’il en soit, je suis, pour ma part, toujours très impressionné par l’évolution de JavaScript et de son écosystème au sein des navigateurs web (je ne parle pas du tout ici, bien entendu, de tout ce qui peut se passer coté serveur, cf node.js principalement).

2 commentaires sur cet article

  1. Eric, le mardi 28 janvier 2020 à 12:07

    «  »$(”div .class”) […] On ne pouvait tout simplement pas faire cela en JavaScript natif (sur aucun navigateur) »
    Faux. Sans librairie, on peut faire aussi ainsi :
    document.body.getElementsByTagName(”div”)[0].getElementsByClassName(”class”)

    C’est d’ailleurs beaucoup plus rapide ainsi. Et même plus rapide que querySelector.

  2. Stéphane, le mercredi 11 novembre 2020 à 13:08

    Éric, il me semble que ton argument n’est pas bon.

    En effet, mettons ce code :

    <div><p> […] </p></div>
    
    <div><p class="class"> […] </p></div>

    Le querySelector ou le sélecteur jQuery trouveront le P dans la deuxième DIV, cependant d’après ton code, ton sélecteur dit qu’il n’existe pas, puisqu’il cherche dans la première.