JS protip : Récupérer les derniers éléments d’un tableau
Par Christophe Porteneuve • Publié le 19 octobre 2022
• 3 min
This page is also available in English.
Il est très fréquent qu’on aie besoin d’aller récupérer un ou plusieurs éléments à la fin d’un tableau ou d’une String
. Historiquement, c’était un peu la lose, mais les choses ont changé !
À l’ancienne…
Commençons par les recettes de grand-mère. Mais pour le coup, ce n’est pas forcément dans les vieux pots qu’on fait les meilleures soupes !
Tu le sais peut-être, les méthodes slice()
(sur Array
et String
) et splice()
(sur Array
) autorisent les indices négatifs : ils se comportent comme s’ils étaient préfixés par length
, de sorte que −1 désigne le dernier élément, −2 l’avant-dernier, etc. :
// 😐 Meh. Indices négatifs certes, mais via `slice()`.
const first = 'Maxence'
const wonders = [first, 'Louise', 'Anna', 'Elliott']
wonders.slice(-1) // => ['Elliott']
wonders.slice(-2) // => ['Anna', 'Elliott']
wonders.slice(-2, -1) // => ['Anna']
first.slice(-1) // => 'e'
Hélas ! Outre que Array#slice()
enverra un tableau (alors qu’on veut souvent un seul élément), ce qui nous obligerait à faire un items.slice(-1)[0]
un peu gore, ça ne fonctionnera de toutes façons pas pour l’opérateur d’indexation indirecte, []
, qu’on utilise le plus souvent pour aller chercher un élément dans un tableau (voire une String
) :
// ❌ Ah bah non.
const first = 'Maxence'
const wonders = [first, 'Louise', 'Anna', 'Elliott']
wonders[-1] // => undefined
first[-1] // => undefined
La raison est simple : la sémantique de []
n’a rien à voir avec des positions. Cet opérateur prend un nom de propriété, et soit la propriété est là (et on récupère sa valeur), soit elle ne l’est pas (et on récupère undefined
).
C’est pourquoi on en était réduit à cette sempiternelle saleté :
// 😭 C'est verbeuuuuuuux
const first = 'Maxence'
const wonders = [first, 'Louise', 'Anna', 'Elliott']
wonders[wonders.length - 1] // => 'Elliott'
first[first.length - 1] // => 'e'
Pas glop™.
Des indices négatifs avec un enrobage proxy ?
Depuis ES2015, nous avons les proxies ES (voir mon talk en anglais à Fronteers 2019 ou la doc MDN en français, en attendant mon talk en français à BDX I/O 2022).
Si nous pouvons accéder à un objet enrobé par le proxy idoine, on a tout-à-coup la possibilité d’utiliser une indexation négative ! À toi de voir si ça te botte lorsque tu renvoies des tableaux par exemple, pour autoriser ce type d’accès au code qui t’appelle.
// 🤯 Un petit proxy ES qui va bien, blindé et tout…
function allowNegativeIndices(obj) {
return new Proxy(obj, {
get(target, prop, receiver) {
return Reflect.get(target, checkIndexAccess(target, prop), receiver)
},
set(target, prop, receiver) {
return Reflect.set(target, checkIndexAccess(target, prop), receiver)
},
})
}
function checkIndexAccess(target, prop) {
return typeof prop !== 'symbol' && prop < 0
? target.length + Number(prop)
: prop
}
Voyons ce que ça donne :
// 😎 Les indices négatifs en force !
const wonders = allowNegativeIndices(['Maxence', 'Louise', 'Anna', 'Elliott'])
wonders[-1] // => 'Elliott'
at()
sur les itérables de la bibiothèque standard
Bon, on n’a pas toujours le luxe, ou simplement l’envie, d’enrober nos tableaux avec un proxy sur-mesure. Alors on fait quoi ?
Eh bien, depuis ES2022, nous disposons de la méthode at(index)
sur tous les itérables positionnels définis par la bibliothèque standard : Array
, tableaux typés et String
.
// 😎 I READ THE DOCS! 🦸🏻
const first = 'Maxence'
const wonders = [first, 'Louise', 'Anna', 'Elliott']
first.at(-1) // => 'e'
wonders.at(-2) // => 'Anna'
findLast()
et findLastIndex()
ES2023 va officialiser deux nouvelles méthodes utilitaires sur Array
et les tableaux typés, qui facilitent la recherche depuis la fin du tableau, plutôt que depuis le début.
Après tout, on avait bien lastIndexOf()
et reduceRight()
depuis ES5, mais ES2015 n’avait pas fourni de versions « depuis la fin » de ses ajouts find()
et findIndex()
. Il était donc temps, parce qu’autrement on en était réduit à faire des boucles à la main 🤮 ou à faire un reverse()
coûteux (et mutatif) en premier lieu !
// 😎 Je suis les avancées d'ECMAScript…
const wonders = ['Maxence', 'Louise', 'Anna', 'Elliott']
wonders.findLast((name) => name.match(/[^aeiouy]/i)) // => 'Louise'
wonders.findLastIndex((name) => name.length < 7) // => 2
findLast()
et findLastIndex()
ne seront certes officiels qu’en juin 2023, mais sont déjà pris en charge nativement partout (Safari 15.4, Firefox 104, Chrome/Edge 97, Node 18, Deno 1.24), et pour le reste, c’est polyfillable trivialement avec core-js (via Babel / TS ou non).
Des astuces en veux-tu en voilà !
On a tout plein d’articles et de vidéos existants et encore beaucoup à venir. Pour ne rien manquer, tu devrais penser à t’abonner à notre newsletter, à notre chaîne YouTube, nous suivre sur Twitter ou encore mieux, à suivre une de nos formations du feu de dieu 🔥 !