Pourquoi tu ne devrais jamais utiliser new sur les types JavaScript natifs
Par Delicious Insights • Publié le 22 nov. 2012

Qu’est-ce qu’on a comme types natifs en JavaScript ? Facile : Object, Number, String, Boolean, Array, Date, RegExp et Function.

Et lorsqu’on instancie un objet en JavaScript, on fait généralement new LeType(…). Mais dans cet article, je vais vous expliquer pourquoi, à quelques petites exceptions près, pour les types natifs c'est soit inutilement lourd, soit carrément contre-productif.

Object

De deux choses l’une :

  • soit tu veux instancier un type à toi, et tu vas faire new TonType, pas new Object ;
  • soit tu veux bien un objet basique tout nu, et alors tu auras largement plus vite fait d'utiliser {} (ex. var obj = {};). La version new est inutilement verbeuse, car on a une syntaxe litérale disponible. Ce sera aussi le cas de plusieurs autres types natifs.

Number

OK, donc tu veux créer un nouveau nombre. Explique-moi pourquoi tu irais faire new Number(42) au lieu de juste 42 ?!? Tu es payé(e) au caractère ? Évidemment pas.

En plus, les deux ne se comportent pas de la même façon dans certains cas. Par exemple, le protocole de conversion booléenne n'est pas le même : if (new Number(0)) passera alors que if (0) non.

String

Là aussi, on a une syntaxe litérale : les apostrophes ou guillemets (aucune différence dans le comportement). Aucune raison de t'embarasser d'un new String('foobar') lorsque l'argument, 'foobar', suffit.

Qui plus est, JavaScript va traiter différemment les objets String (issus d'un new String(…)) et les chaînes primitives (produites par un litéral). Ainsi, typeof new String('foo') produit 'object' et non 'string'. Et eval ne considèrera comme code source qu'une chaîne primitive, alors qu'un objet String sera juste renvoyé tel quel. Autant de raisons de ne jamais recourir à new String

Boolean

On est dans une situation similaire à Number : non seulement les deux litéraux réservés true et false sont suffisants, mais en plus le protocole de conversion booléenne va nous foutre dedans : if (new Boolean(false)) passera ! Une bonne raison de ne jamais recourir à new sur ce coup…

Array

Dans la mesure où on a une syntaxe litérale (crochets et virgules, ex. [1, 2, 3]), il n'y a a priori jamais à utiliser new Array(1, 2, 3)

Il existe juste un cas, finalement assez rare, où le new est toutefois justifié : pour préallouer un tableau à une certaine taille, ce qui se produit si on utilise un unique argument numérique. Par exemple, new Array(1000000) va créer un tableau d'un million de cases, toutes pour le moment undefined. Encore une fois, c'est un cas de figure assez rare.

Date

Pour le coup, new Date est parfaitement justifié, puisqu’on initialise très souvent un objet Date sur la base d'une valeur précise, que celle-ci soit un numérique (nombre de millisecondes depuis l'epoch, le 1er janvier 1970 à 0h GMT), une chaîne de caractère compatible ou une série de composantes année, mois, jour, etc. Il n'y a tout simplement pas de syntaxe litérale pour les dates, on doit donc recourir à new Date.

Il existe toutefois un cas où je déconseille new Date : lorsqu'on veut juste récupérer la version numérique du moment présent. Plutôt que de faire new Date().getTime(), préférez Date.now() : non seulement c'est nettement plus compact et explicite, mais en plus ça évite de créer inutilement un objet Date à garbage-collecter ensuite…

RegExp

On a ici deux cas de figure :

  • La regex est fixe, connue à l'avance. Il est alors inutile de passer par new RegExp, puisqu'on dispose d'une syntaxe litérale. /foo(?:bar)?/ig sera toujours plus court que new RegExp('foo(?:bar)?', 'ig')… Cependant, si la regex est bourré de slashes (/), je peux comprendre qu'au bout d'un moment on préfère éviter de tous les échapper : new RegExp('^https?://foo/bar/baz') peut sembler préférable à /^https?:\/\/foo\/bar\/baz/
  • La regex n'est pas connue à l'avance. Par exemple, elle est calculée sur la base d'une saisie utilisateur, d'une réponse Ajax, etc. Auquel cas on n'a pas d'autre choix que de passer par new RegExp. Imaginons qu'on développe un fuzzy matcher qui cherche toutes les chaînes de caractères contenant ceux qu'on saisit, dans le bon ordre mais quelles que soient leurs positions, comme dans les éditeurs TextMate et SublimeText :
var words = ['déjà', 'demain', 'délicat', 'dommage', 'foobar'];
var input = 'da'; // On l'aurait par exemple prix de $('#search').val()…
var regex = new RegExp(input.split('').join('.*'), 'i'); // => /d.*a/i

var result = [], word;
for (var index = 0, len = words.length; index < len; ++index) {
  word = words[index];
  if (regex.test(word))
    result.push(word);
}

// Ou avec Prototype : var result = words.grep(regex);

result // => ['demain', 'délicat', 'dommage']

Function

Il existe trois manières de créer des fonctions en JavaScript :

  • Avec une déclaration : c'est ce qui se passe quand le mot-clé function est rencontré à un endroit où on attend une instruction. Il doit alors obligatoirement être suivi d'un identifiant qui nomme la fonction. Les déclarations de fonction sont hoisted, c'est-à-dire qu'elles sont interprétées comme si elles étaient placées en tout début de portée (donc de l'éventuelle fonction conteneur).
  • Avec une expression : c'est le cas quand le mot-clé function est rencontrée alors qu'on attend une expression (par exemple, à droite d'un opérateur =, d'une parenthèse ouvrante, ou comme argument à une autre fonction). La fonction n'est pas obligatoirement nommée (et si elle l'est, ce nom n'est valable qu'à l'intérieur de la fonction elle-même), et n'est pas non plus hoisted.
  • Avec new Function. Cela permet de créer une fonction dont le code a été déterminé dynamiquement à l'exécution, mais attention, il existe une différence majeure : là où les déclarations et expressions de fonction entraînent une closure (une fermeture lexicale, qui leur permet d'accéder à l'ensemble de la portée), les fonctions obtenues par new Function s'exécutent dans le contexte global (dans un navigateur, l'objet window), voire, si on est en EcmaScript 5 et en mode strict (le texte de la fonction démarre par 'use strict';), avec un contexte undefined.

L'immense majorité du temps, votre fonction a besoin de sa closure : vous aurez donc recours à une expression de fonction ou à une déclaration.

Dans certains cas très rares toutefois, tels qu'une évaluation de code arbitraire que vous souhaiteriez « protéger » en lui interdisant tout accès à la closure en vigueur, recourir à new Function, surtout en mode strict si ça vous est possible, est une alternative viable, et souvent préférable, à un bête eval.

Conclusion

Comme vous pouvez le voir, au quotidien, il est généralement inutile de recourir à new pour les types natifs, à l'exception notable de new Date et, de temps en temps, de new RegExp. Les cas valables de new Array et new Function sont rares, pour ne pas dire rarissimes, et tous les autres cas sont inutiles voire contre-productifs, au sens où ils entraînent des comportements inattendus et sources de bugs.

J'espère que cette petite balade à travers la sémantique des types natifs et les distinctions, souvent subtiles, entre les valeurs dites primitives et leurs équivalents objets vous aura intéressé, et qui sait, peut-être même éclairé sur des accrocs passés.

Envie d’en apprendre davantage ?

Notre formation JS Total explore en profondeur tous les aspects techniques avancés du langage JavaScript lui-même, et toute la stack technique web front. Du bon usage de new à la programmation fonctionnelle en passant par les méthodes méconnues des types natifs et le fonctionnement des prototypes, cette formation JavaScript vous donne un gros coup de boost dans le développement de vos codes JS quels qu’ils soient.

{% session_dates js-total %}