Les joies de slice et splice
Par Delicious Insights • Publié le 9 sept. 2014

Si vous en êtes encore à faire du substring(…), à vous galérer à passer des arguments du genre bidule.length - 1, à regretter que pop() et shift() ne sortent qu'un élément, ou à faire à la main des boucles de modification pour des portions de tableau, cet article est pour vous.

Et sinon, il est pour vous aussi.

Sur quoi utiliser slice ?

Sur String et Array. Il a exactement la même sémantique sur les deux, ce qui est pratique : on peut écrire du code générique qui traite indifféremment un tableau classique, ou considère une String comme un tableau de caractères.

La signature est donc la même :

slice([start[, exclusiveEnd]]) // => Array|String

La position de fin est exclue, comme toujours (ex. substring(…)), afin de faciliter les boucles qui utilisent des pivôts (genre on réutilise la fin d'un tour comme début de l'autre, sans chevauchement).

Par exemple :

var names = ['Alice', 'Bob', 'Christophe', 'David', 'Élodie'];

names.slice(3, 5)    // => ['David', 'Élodie']
names[4].slice(0, 3) // => ['Élo']

Vous remarquez qu'on ne risque rien avec les caractères Unicode.

Arguments optionnels

Comme c'était aussi le cas de substring(…), slice(…) n'exige pas son 2ème argument : si celui-ci est omis, il vaut par défaut le length du sujet. En gros, on prend tout jusqu'à la fin :

var names = ['Alice', 'Bob', 'Christophe', 'David', 'Élodie'];

names.slice(3)    // => ['David', 'Élodie']
names[4].slice(1) // => ['lodie']

En fait, même le premier argument est optionnel, et vaut zéro par défaut. Quel intérêt, puisque ça reprendrait tout le tableau ? On verra ça dans un instant.

Indices négatifs

C'est là que slice commence à envoyer du pâté. Saviez-vous qu'on peut utiliser des indices négatifs ? Pour les comprendre, c'est simple : -x correspond à sujet.length - x. Du coup, -1 correspond à la dernière position, -2 à l'avant-dernière, etc.

Imaginons que vous souhaitiez récupérer les deux derniers éléments de massivelyNamedArray :

// La version pourrie :
massivelyNamedArray.slice(massivelyNamedArray.length - 2, massivelyNamedArray.length)

// La version bof :
massivelyNamedArray.slice(massivelyNamedArray.length - 2)

// La version licorne :
massivelyNamedArray.slice(-2)

Vous avez un nom de balise sous la main dans tagName et vous voulez en extraire les chevrons, garantis aux bords ?

// Si vous avez du temps à perdre…
tagName.substring(1, tagName.length - 1)

// La version bof :
tagName.slice(1, tagName.length - 1)

// La version poney :
tagName.slice(1, -1)

En plus, dans le cas présent, ce 1/−1 est bien explicite par rapport au souhait d'origine, vous ne trouvez pas ?

En attendant Array.from

En ES6, on aura le plaisir d'avoir enfin un Array.from(…) natif, un adaptateur universel qui prend un truc « genre tableau » (un tableau générique quoi : une propriété .length entière non négative, et des propriétés numériques) pour nous retourner un vrai tableau (une instance de Array).

Un cas extrêment fréquent aujourd'hui consiste à pouvoir manipuler comme un tableau (en utilisant les méthodes traditionnellement dévolues à Array) l'objet automatique arguments, présent dans le contexte d'invocation de chaque fonction.

Hélas, pour le moment cet objet est de type Arguments au lieu d'être de type Array. C'était une décision débile, mais c'est comme ça. On ne peut donc pas faire, de base, un truc du genre arguments.map(…).

Du coup, on cherche à obtenir une version Array de notre objet arguments. La manière la plus simple consiste à appeler la méthode slice des tableaux, mais avec arguments comme sujet (comme this, si vous préférez). En effet, toutes les méthodes de Array sont génériques : elles n'ont pas besoin que leur sujet soit véritablement un Array, juste qu'il adhère au contrat des tableaux génériques.

Ça donne ça :

var args = Array.prototype.slice.call(arguments);

C'est ici qu'on réalise l’intérêt d’un appel sans aucun argument à slice. Puisque ce dernier renvoit, de façon garantie, une instance de Array, on vient de s'en servir pour convertir un tableau générique en « véritable » tableau.

(Bien sûr, si le premier argument avait été obligatoire, on aurait juste eu à faire .call(arguments, 0)…)

splice, le gros couteau suisse

La méthode splice est autrement plus puissante que slice, mais surtout, elle est mutative : elle modifie le tableau en place (alors que slice ne touche jamais au tableau d'origine).

La signature peut sembler étrange :

array.splice(index, howMany[, element1[, ...[, elementN]]]) // => Array

Ici, on a :

  1. La position de départ, obligatoire (vraiment)
  2. Le nombre d'éléments qui vont être spliced ; donc une quantité, pas une position. On verra tout l'intérêt de ce choix plus tard. Cet argument est obligatoire, sauf dans Firefox, ou s'il est omis, il prend tout le reste du tableau à partir de la position de départ, incluse. Ce n'est toutefois pas présent dans le standard EcmaScript, alors évitez !
  3. D'éventuels éléments à insérer/remplacer, en lieu et place de ceux qui ont été spliced

Grâce à cette signature apparemment hétéroclite, on peut facilement réaliser, en une fois, avec des performances optimales et moins de risques de bugs que si on se fadait notre propre boucle, n'importe quelle opération de modification.

Et qui plus est, l'index peut là aussi être négatif !

Retirer une partie du tableau

Il suffit de ne préciser aucun élément de remplacement. Voyez plutôt :

var names = ['Alice', 'Bob', 'Christophe', 'David', 'Élodie'];

// Retirons le 3ème élément
names.splice(2, 1)  // => ['Christophe']
names               // => ['Alice', 'Bob', 'David', 'Élodie']

// Multi-pop, anyone?  Voici une sorte de "pop(2)"
names.splice(-2, 2) // => ['David', 'Élodie']
names               // => ['Alice', 'Bob']

Notez que pour le dernier exemple, sur Firefox uniquement, on aurait pu faire names.splice(-2), mais ne prenez pas cette mauvaise habitude…

Remplacer une partie du tableau

Facile : on précise simplement les éléments de remplacement. Ils n'ont pas besoin d'être aussi nombreux que ceux remplacés : il peut y en avoir plus, ou moins…

var names = ['Alice', 'Bob', 'Christophe', 'David', 'Élodie'];

// Allez, on remplace Bob et Christophe par Bill et Charles
names.splice(1, 2, 'Bill', 'Charles') // => ['Bob', 'Christophe']
names                                 // => ['Alice', 'Bill', 'Charles', 'David', 'Élodie']

// Ou on reste entre filles…
names.splice(1, 3, 'Claire') // => ['Bill', 'Charles', 'David']
names                        // => ['Alice', 'Claire', 'Élodie']

Insérer à un endroit du tableau

Pour insérer dans un tableau, il suffit de ne remplacer aucun élément : on précisera zéro comme deuxième argument, puis les éléments à insérer, en direct (pas sous forme d'un tableau).

var names = ['Alice', 'Élodie'];

names.splice(1, 0, 'Bob', 'Claire', 'David') // => []
names                                        // => ['Alice', 'Bob', 'Claire', 'David', 'Élodie']

Envie d'en savoir plus ?

On fait des formations JS magnifiques, qui font briller les yeux et frétiller les claviers :

  • JS Total, pour apprendre tout ce qu'il faut pour faire du dev web front moderne, efficace, rapide et haut de gamme.
  • Node.js, pour découvrir, apprivoiser puis maîtriser le nouveau chouchou de la couche serveur, qui envoie du gros pâté !