Je ne vais pas paraphraser les excellents guides et tutoriels du MDN sur le Javascript, et je vous invite donc à les consulter pour les détails. Mais essayons de voir quelques “perles” du JS, et quelques astuces qui pourraient vous alléger vos codes.

Référence du langage

Vérifiez que vous connaissez les différents mots-clefs du JS, comme let, const, for...of... etc, ainsi que les nouveautés d’ES6. Vous éviterez de perdre inutilement du temps sur des bugs issus d’un manque de compréhension de votre part du langage JS.

=>

Cet opérateur, appelé “Arrow Function”, est un sucre syntaxique équivalent à une fonction anonyme.

n => n+2
function (n) { return n+2; }
() => 1
function () { return 1; }
(a,b) => a+b
function (a, b) { return a+b; }
Ces codes sont deux à deux identiques

async function

Le but du mot-clef async devant une fonction est de la rendre asynchrone: elle sera exécutée en arrière-plan, et le fil principal de votre JS continuera. Le mot-clef await vous permettra éventuellement d’attendre la fin de la fonction asynchrone. Cela vous évitera les piles de callbacks ou de Promise.

function*

function* et yield permettent de créer un générateur, utilisable par exemple dans un for ... in ....

class

class offre la possibilité de déclarer des classes en JS, avec héritage (cf aussi super).

Spread Operator

... est appelé “spread operator”, et permet de passer ou de recevoir un tableau comme une liste de variables. Par exemple: myfunction(...[a,b,c]) équivaut à myfunction(a,b,c).

Destructuring assignment

[a,b,c] = [1,2,3] ou encode {a,b,c} = {a:1,b:2,c:3} sont des assignements acceptables.

Comma operator

Le “Comma Operator” (ou ,) permet de joindre plusieurs “statements” en un seul, et de retourner la valeur du dernier. Par exemple: n => (k = n+1, k*k+n) équivaut à function (n) { var k = n+1; return k*k+n; }.

Events

Voir l’article détaillé sur les Events JS

Classes et interfaces JS

Promise

Une Promise est simplement un objet javascript, créé instantanément, et qui sera “résolu” plus tard, quand une opération sera terminée. Quand cette “résolution” aura lieu, une callback sera appelée.

Cette interface est très utilisée par les APIs natives du VanillaJS. Tâchez de comprendre son principe de fonctionnement avant de plonger dans ces autres APIs.

DOM Interfaces

ChildNode

L’interface ChildNode (voir aussi Node) est implémentée, grosso modo, par tous les noeuds du DOM. Elle vous permet de les manipuler facilement, en retirant un noeud du DOM (

myNode.remove()
), en rajoutant un noeud (insertAdjacentElement, insertBefore), etc.

CharacterData

De façon similaire, les noeuds CDATA peuvent être manipulés par l’interface correspondance CharacterData, pour en changer le contenu par exemple.

DOMClassList / DOMTokenList

Ces deux interfaces DOMClassList et DOMTokenList permettent de manipuler des attributs HTML sous la forme d’une liste de tokens. Vous pourrez alors facilement ajouter ou retirer des classes à un noeud HTML ou des mots-clefs à un attribut rel.

DOMException

L’interface DOMException vous permet de gérer plus facilement vos erreurs, via des exceptions ayant un message et un code.

Géométrie

Plusieurs interfaces vous permettront de faire du calcul géométrique facilement et rapidement en javascript, par exemple DOMPoint et DOMMatrix. Vous les retrouverez aussi dans les manipulations du SVG. N’allez toutefois pas perdre du temps en réinventant un moteur 2D ou 3D!

L’article du MDN sur le DOM liste de nombreuses autres interfaces utiles.

XPath

Il est possible d’utiliser des expressions XPath en Javascript, pour sélectionner des éléments de la page. Face aux sélecteurs CSS, les sélecteurs XPath ont l’avantage de proposer des axes descendants (parent vers enfant type body//p) mais également ascendants (des noeuds enfants vers les noeuds parents, type p[ancestor::body]). Ils sont donc plus “puissants” que les sélecteurs CSS, puisqu’ils offrent plus de possibilités. Voyez les articles du MDN pour plus d’informations.

var x = document.createExpression('//text()[ancestor::p]', null)
    .evaluate(document.body, XPathResult.ANY_TYPE, null);
var n = null;
while (n = x.iterateNext()) {
  console.log(n);
}
Sélecteur XPath (tous les noeuds textes descendants d’un noeud p) et itération

Accessibilité

Tout ce qui est bricolé en Javascript n’est pas facilement accessible (et fait hurler l’ergonome en moi). Les jeux à base de canvas seront très difficiles à porter sur d’autres supports (que ce soit mobile, tablette, PC, lecteur d’écran ou commande vocale) et risquent d’amener énormément de maintenance (présente et à venir).

Les “hooks” (eventListener) Javascript sont peu compatibles avec les commandes vocales de Windows

AJAX n’est pas indispensable

Ne recodez pas un navigateur dans votre site web via Javascript. Usez des

iframe
à la place (à ne pas confondre avec les
frame|frameset
, qui sont dépréciées), et assurez-vous que votre jeu reste utilisable même si vous dégagez ces appels AJAX: s’ils servent à soumettre des formulaires, alors créez simplement un formulaire HTML, et rajoutez une surcouche pour sa soumission. En supprimant cette surcouche AJAX, le formulaire HTML classique sera utilisé et tout fonctionnera normalement. Notez tout de même que le spinner du navigateur ne se déclenchera pas, sauf si vous rajoutez un autre bricolage façon ABLI (Ajax Browser Loading Indicator). Personnellement, je trouve cela bien trop lourd et inutile: autant mettre un simple indicateur (classe CSS) sur les boutons du formulaire pour indiquer qu’il charge.

Au final, n’allez donc pas croire que je rejette tout code AJAX (Isometry, ECLERD, VariiSpace… s’en servent), mais il faut lui laisser le simple rôle de surcouche, et non le fondre dans la masse du jeu (qui vous amènera beaucoup de complexité inutile!).

Un exemple de soumission de formulaire sans AJAX, par iframe.

XMLHttpRequest

Dans les cas où AJAX vous serait nécessaire, oubliez jQuery et servez-vous directement de XMLHttpRequest (l’objet natif pour AJAX). Vous pourrez comprendre et piloter intégralement vos requêtes client/serveur, headers HTTP inclus (entre autres).

FormData

L’API FormData peut se coupler à XMLHttpRequest pour permettre à l’utilisateur d’entrer des informations (via un formulaire classique), puis de récupérer ces informations dans votre code JS, et de les transférer au serveur via XMLHttpRequest. Vous aurez alors la possibilité de traiter la réponse du serveur dans votre code JS. Cette technique est utilisée sur Iamanoc pour envoyer les formulaires. Si le formulaire a été correctement rempli et traité par le serveur, alors le code JS renvoie l’utilisateur vers la “bonne” page web (indiquée par le serveur), et sinon, le code JS affiche le message d’erreur du serveur à l’utilisateur, lui évitant de changer de page et de devoir remplir à nouveau son formulaire.

sendbeacon

Un peu similaire à AJAX, navigator.sendBeacon() vous permet d’envoyer des données au serveur, en GET, de manière asynchrone et simplissime, mais sans pouvoir récupérer la réponse du serveur. Cela sert surtout à envoyer des informations de métriques à votre serveur, de manière similaire à l’attribut ping de la balise <a>, sans nécessiter une action de la part de l’utilisateur.

Server-Side Events

Server-side events est une API javascript plutôt prometteuse, permettant de conserver une connexion active et d’y transférer des informations en provenance du serveur sous forme d’event javascript. Cela permet donc au serveur de “pusher” des Events qui déclencheront quelque chose côté client, suivant les listener que l’on aura créés. En pratique, cette technologie n’est pas pratique à utiliser sur la stack WAMP: elle verrouille une connexion client-serveur, et elle oblige le côté PHP du serveur à avoir une boucle

while(true)
, dans laquelle il vous sera souvent nécessaire de faire une query SQL, rendant le tout sous-optimal (voir l’exemple à installer sur votre PC). Si vous avez besoin de ce genre de techno, il vaut donc mieux envisager de changer de stack (NodeJS ou SDK de jeu AAA).

Ajouter un BOM

Pour certains fichiers UTF-8, il peut être nécessaire d’ajouter un BOM lorsqu’un javascript génère le contenu du fichier (histoire que ce dernier soit bien lu comme du UTF-8). Pour cela, préfixez simplement les données du Blob par \uFEFF, qui est le caractère UTF-8 générant le BOM binaire 0xEF 0xBB 0xBF:

new Blob(["\ufeff", fileData], {type: 'text/csv'})
. Si le BOM est plus exotique, vous pouvez passer par un Uint8Array:

var myBom = new Uint8Array(3);
myBom[0] = 0xef;
myBom[1] = 0xbb;
myBom[2] = 0xbf;
var blob = new Blob([myBom, fileData], {type: 'text/csv'});

Classes en Javascript

Ne cherchez pas la notion de “classe” au sens PHP dans le langage Javascript: cela n’existe pas. Vous aurez des framework qui tenteront de vous les émuler, ou du sucre syntaxique en ECMA6, mais mieux vaut simplement comprendre le paradigme de Javascript plutôt que d’essayer de le violer contourner: tout est objet en Javascript. Donc, vous n’allez pas créer une classe, mais plutôt un objet (~prototype), dont les autres objets (~instances) hériteront.

“Click” Event

L’Event “click” peut être triggé par un terminal tactile (touch), vocal, ou autre: ce n’est pas forcément un click souris, malgré son nom.

window.addEventListener('load', function () {
  document.querySelector('.action').addEventListener('click', function() {
    Notification.requestPermission(function () {
      new Notification("Document loaded", {
        body: "Hello, world!",
        icon: "/wp-content/uploads/2017/01/reinom-jeuweb-logo.png"
      });
    });
  });
})();
Exemple d’utilisation de la Notification API

Optimiser Javascript

Javascript est très performant, à condition de bien l’utiliser en évitant les écueils suivants:

  • Evitez les frameworks lourds (dont jQuery): ECMA6 propose nativement la quasi totalité de ce que ces framework émulaient (et ce qui n’est pas proposé est souvent une mauvaise pratique, à éviter donc)
  • Limitez les manipulations du DOM: créez un noeud parent, ajoutez tous les enfants dont il a besoin, et insérez-le en dernier lieu dans le document
  • Faites des fonctions traitant des ensembles d’objets (
    function ...() { for... }
    ), plutôt qu’une seule que vous appelez dans une boucle (
    for ... { function...() }
    ). Le même principe s’applique d’ailleurs à PHP.
  • Servez-vous des formes courtes:
    [ ]
    et
    { }
    à
    Array
    et
    new
  • Simplifiez les boucles via
    for (var i=0, s=array.length; i&lt;s; i--)
    pour ne pas recalculer array.length à chaque itération.
  • Cachez les objets globaux:
    var w = window
    puis utilisez w dans les boucles au lieu de window, qui doit sinon être résolu à chaque itération
  • Ordonnez correctement les tests: ceux qui risquent le plus d’échouer en premier
  • N’utilisez pas le mot-clef
    with
  • Limitez les try { ... } catch (...) { ... }: ne vous en servez pas comme d’un return
  • Appliquez strictement le standard actuel (ECMA-262 6e édition en 2016)
  • Servez-vous du profiler du navigateur pour savoir quelles parties du code sont vraiment lentes
  • Préoccupez-vous de l’optimisation que si nécessaire: n’anticipez pas inutilement les problèmes de performances, et ne les réglez que s’ils sont vraiment humainement perceptibles
  • La limite mémoire de Javascript est similaire à celle du programme (navigateur) qui l’exécute, donc éviter de charger des ressources (vidéos, sons, images) dans la mémoire JS!