Introducción
Con la reciente adopción del nuevo estándar ECMAScript2015, ‘encontramos una no mejora’ que siempre es conveniente resaltar: continuamos sin tener un método nativo para eliminar valores de un array dado.
Como esta funcionalidad es recurrente y necesaria, vamos a implementar una simple función en Javascript plano que nos resuelva el problema…
El problema
Tenemos este array:
var foo = [ 'thumb-1', 'thumb-2', 'thumb-3', 'thumb-4' ]; |
Y queremos borrar un elemento; digamos por ejemplo ‘thumb-2‘.
Opción mala
Ya que no tenemos un método ‘remove‘ en el objeto global Array, necesitamos utilizar alguno de los disponibles. El más recurrente sería en este caso ‘splice’, el cual cambia el contenido de un array eliminando o agregando elementos:
function removeItemFromArr ( arr, item ) { var i = arr.indexOf( item ); arr.splice( i, 1 ); } removeItemFromArr( foo, 'thumb-2' ); console.info( foo ); // ["thumb-1", "thumb-3", "thumb-4"] |
El problema con esta función es que si tratamos de borrar un elemento que no existe, el resultado no será el deseado:
removeItemFromArr( foo, 'picture-1' ); console.info( foo ); // ["thumb-1", "thumb-2", "thumb-3"] |
Obsérvese que el último elemento, ‘thumb-4‘, ha desaparecido. Esto es así porque el valor que toma ‘i‘ tras no encontrar ‘indexOf‘ una concurrencia, es -1. Por lo tanto, al método splice le estamos indicando que borre ‘-1’ o, lo que es lo mismo, el último elemento del array (o el primero empezando por el final).
Opción más segura
Para evitar el problema con elementos que no existen, necesitamos comprobar el valor de ‘i‘ antes de ejecutar el splice:
function removeItemFromArr ( arr, item ) { var i = arr.indexOf( item ); if ( i !== -1 ) { arr.splice( i, 1 ); } } removeItemFromArr( foo, 'picture-1' ); console.info( foo ); // ["thumb-1", "thumb-2", "thumb-3", "thumb-4"] removeItemFromArr( foo, 'thumb-2' ); console.info( foo ); // ["thumb-1", "thumb-3", "thumb-4"] |
No está mal; es una opción válida. Podemos formatear un poco la función para que tenga mejor aspecto sin alterar su comportamiento:
var removeItemFromArr = ( arr, item ) => { var i = arr.indexOf( item ); i !== -1 && arr.splice( i, 1 ); }; |
Opción no mutable
Otro problema que podemos encontrar ahora es el de la no inmutabilidad: la función anterior toma un array y lo modifica, haciendo imposible volver a su estado original. En ciertos escenarios y paradigmas, como bajo la Programación Funcional, el concepto de mutabilidad no está permitido.
Propongamos una primera versión inmutable de nuestra función utilizando para ello el método Array.filter():
function removeItemFromArr( arr, item ) { return arr.filter( function( e ) { return e !== item; } ); }; var newFoo = removeItemFromArr( foo, 'thumb-2' ); console.info( foo ); // ["thumb-1", "thumb-2", "thumb-3", "thumb-4"] console.info( newFoo ); // ["thumb-1", "thumb-3", "thumb-4"] |
La cosa funciona correctamente y, de nuevo, es tolerante frente a elementos que no se encuentren en el array original:
var newFoo = removeItemFromArr( foo, 'picture-1' ); console.info( newFoo ); // ["thumb-1", "thumb-2", "thumb-3", "thumb-4"] |
Reformateamos para que quede más limpia:
var removeItemFromArr = ( arr, item ) => { return arr.filter( e => e !== item ); }; |
Conclusión
Aquí hemos presentado unos cuando métodos para borrar elementos de un array intentando así suplir una carencia histórica del lenguaje Javascript. Es cierto que con los nuevas estructuras de datos de tipo Set (ECMA-262), este problema se resuelve parcialmente al contar ya con un método ‘delete’ nativo; no obstante, su comportamiento y semátinca difiere en parte de los arrays tradicionales.
He entrado buscando algún comentario sobre rendimiento, quizás implementaciones alternativas y las diferencias en rendimiento.
Splice siempre va a ser lento como el demonio. Si tu código require muchas inserciones, definitivamente Array no es la estructura de datos correcta. Una lista enlazada siempre irá mejor.
Sí; quizá puedo añadir un apartado sobre rendimiento en diferentes métodos… tienes razón cuando trabajamos con arrays grandes.
Gracias por el apunte!