Métodos para modificar un Array en ES6
Métodos que modifican el propio Array
Los métodos que modifican el Array sobre el que se aplica son aquellos que realizan cambios en sus propios elementos. Algunos métodos devuelven una referencia al mismo Array modificado. Los métodos básicos de manipulación de elementos son push()
y pop()
que nos permiten agregar y eliminar al final del Array, mientras que unshift()
y shift()
lo hacen al inicio. En la Figura puede ver esto esquemáticamente. Como ejemplos de uso implementaremos una pila y una cola con estos métodos.
Otros métodos nos permiten copiar un rango de elementos y pegarlo en otra posición con copyWithin()
, rellenar elementos con un valor con fill()
, darle la vuelta con reverse()
, ordenar con sort()
y eliminar con splice()
. Con el método sort()
haremos un ejemplo para ordenar una tabla por sus columnas.
- Métodos para modificar el propio Array
- push, pop, shift y unshift para agregar y eliminar elementos. Ejemplos para implementar una pila y una cola.
- copyWithin para copiar y pegar un rango de elementos.
- fill para rellenar elementos con el mismo valor.
- reverse para invertirlo (
darle la vuelta
). - sort para ordenarlo. Ejemplo para ordenar una tabla.
- splice para eliminar elementos.
- Métodos para iterar por un Array
- Métodos para crear nuevos Array
- concat para concatenar dos Array en un nuevo Array.
- map para iterar por un Array ejecutando un callback y devolver un nuevo Array.
- slice para recortar un Array y devolverlo en un nuevo Array. Ejemplo de uso en algoritmo de búsqueda binaria.
- filter para iterar por un Array ejecutando un callback que filtra los elementos, devolviéndolos en un nuevo Array. Ejemplo para filtrar elementos del DOM.
- Métodos para buscar en un Array
- every comprueba que todos los elementos de un Array cumplen un callback.
- some comprueba que algún elemento de un Array cumple un callback.
- find encuentra un valor en un Array que cumpla un callback. Cómo obtener el valor seleccionado en elementos input tipo radio.
- findIndex encuentra el primer índice de un Array que cumpla un callback.
- includes comprueba si un Array tiene un determinado valor.
- indexOf busca el primer índice en un Array que tenga un determinado valor.
- lastIndexOf busca el último índice en un Array que tenga un determinado valor.
- Métodos que devuelven un valor transformado desde un Array
- join transforma un Array devolviendo un String concatenando sus elementos con el String del argumento.
- reduce transforma un Array en un valor final simple mediante un callback. Exponenciación rápida con el método reduce().
- reduceRight transforma un Array en un valor final simple mediante un callback, iterando desde el final del Array hacia el principio.
- toString transforma un Array en un String concatenando sus elementos con una coma.
- toLocaleString transforma un Array en un String concatenando sus elementos en formato local con una coma.
arr.push(elem0, elem1,...)
Método que sirve para agregar al final uno o más elementos. Devuelve el valor length
actualizado.
let arr = ["a"]; console.log(arr.push("b", "c")); // 3 console.log(arr); // ["a", "b", "c"]
Con una serie de argumentos consecutivos como elem0, elem1,...
siempre podemos usar el operador de propagación de elementos de Array. Por ejemplo, en este caso propagamos los elementos del Array letras
en los argumentos de push()
:
let arr = ["a"]; let letras = ["b", "c"]; console.log(arr.push(...letras)); // 3 console.log(arr); // ["a", "b", "c"]
Este método push()
también puede usarse genéricamente con apply()
para concatenar otro Array:
let arr1 = ["a"], arr2 = ["b", "c"]; [].push.apply(arr1, arr2); console.log(arr1); // ["a", "b", "c"]
En lo anterior [].push.apply()
es una forma más cómoda de escribir Array.prototype.push.apply()
. El método push()
puede aplicarse a cualquier objeto. En el siguiente ejemplo tenemos un objeto vacío al que le agregamos valores usando call()
. Vea como crea una propiedad length
si no existe previamente.
let obj = {}; [].push.call(obj, "a"); console.log(obj); // {0: "a", length: 1} [].push.call(obj, "b", "c"); console.log(obj); // {0: "a", 1: "b", 2: "c", length: 3}
El método push
es, como vemos, genérico porque puede aplicarse con apply()
o call()
a cualquier objeto. Especialmente los array-like disponen de una propiedad length
, pero no en todos esa propiedad es modificable. Si intentamos hacerlo en un String se produce un error porque push()
intenta modificar length
pero es un propiedad de sólo lectura en un String:
let str = new String(); [].push.call(str, "a"); //TypeError: Cannot assign to read only property //'length' of object '[object String]'
Sobre objetos y Array podemos modificar la propiedad length
. Los tipos String y Function disponen de una propiedad length
que es de sólo lectura
, como vemos en este código donde se ignora el cambio de valor, aunque no cursa error:
let arr = [], obj = {}; arr.length = 9; console.log(arr.length); // 9 obj.length = 9; console.log(obj.length); // 9 let str = String("a"), fun = function(x){}; str.length = 9; console.log(str.length); // 1 fun.length = 9; console.log(fun.length); // 1
Aunque no podemos modicar directamente la longitud de un String, podemos usar el operador de propagación para convertir el String en un Array y así aplicar muchos de los métodos de Array. Cada caracter del String será un elemento del Array. Luego podemos revertir el Array en un String usando el método join("")
, usando como separador una cadena vacía. Aunque poca utilidad tiene para este método push()
como se observa en el siguiente ejemplo, si que puede ser interesante para otros métodos de Array que deseemos aplicar a los caracteres de un String.
let str = "abc"; let strArr = [...str]; strArr.push("d"); strArr.push("e"); strArr.push("f"); str = strArr.join(""); console.log(str); // "abcdef"
arr.pop()
Método para eliminar un elemento al final del Array, devolviendo dicho elemento.
let arr = ["a", "b", "c"]; console.log(arr.pop()); // "c" console.log(arr); // ["a", "b"]
Con el Array vacío el método pop()
devolverá undefined
. Esto podría servirnos para implementar un bucle que itere por el Array desde el final recuperando cada elemento y, a la vez, vaciando el Array. El siguiente ejemplo revierte las posiciones del Array, haciendo lo mismo que el método reverse()
, aunque no sobre el mismo Array:
let arr = [1, 2, 3, 4, 5], rev = []; while (arr.length){ rev.push(arr.pop()); } console.log(rev); // [5, 4, 3, 2, 1] console.log(arr); // []
Este método pop()
hace lo opuesto a push()
y también es genérico pues pueden ejecutarse con apply()
y call()
a otros objetos array-like. Aunque deben poseer una propiedad length
y, por supuesto, que permite modificarla. Igual que con push()
también no es posible aplicarlo sobre String.
let obj = {0: "a", 1: "b", 2: "c", length: 3}; console.log([].pop.apply(obj)); // "c" console.log(obj); // {0: "a", 1: "b", length: 2}
Implementando una pila con métodos push() y pop()
Los métodos push()
y pop()
son adecuados para implementar una pila (stack) en JavaScript. Se trata de una estructura de datos donde el último en entrar es el primero en salir. Esto se le denomina LIFO de last in first out
. Podemos imaginarlo como un montón de ladrillos, de tal forma que vamos apilándolos y sólo podemos retirar el que está encima de todos (desapilar). Para apilar usaríamos push()
y para desapilar lo haríamos con pop()
.
Un ejemplo de uso clásico de una pila es el algoritmo para transformar una expresión infija en notación polaca inversa o RPN. Un expresión matemática infija es algo como 1 + 2
, mientras su transformación en RPN es 1 2 +
. En esta notación el operador le sigue a dos operandos, mientras que en infija el operador está entre los dos operandos. La notacion RPN es, a su vez, más cómoda para calcular en un programa porque se prescinden de los paréntesis. La transformación infijo-RPN y el posterior cálculo del RPN se realizan ambos usando pilas.
La transformación infijo a RPN se consigue con el algoritmo de shuting yard inventado por Dijkstra. El siguiente código es una versión reducida que no contempla los paréntesis ni el operador "^" para poder explicarlo mejor. El código completo lo puede ver en el enlace del ejemplo:
let pila = [], cima = () => pila[pila.length-1]; let rpn = []; let prec = {"*": 3, "/": 3, "+": 2, "-": 2}; //En tokens hay algo como ["2", "*", "3", "+", "4"] for (let token of tokens){ if ("*/+-".indexOf(token)==-1){ // Operandos rpn.push(token); } else { // Operadores while (pila.length &&(prec[token] <= prec[cima()])) { rpn.push(pila.pop()); } pila.push(token); } } while (pila.length){ rpn.push(pila.pop()); } //En rpn hay ahora ["2", "3", "*", "4", "+"]
Tenemos las variables pila
para manejar los operadores y rpn
donde iremos poniendo la lista de operandos y operadores para obtener la notación RPN final. La función cima()
es muy simple, lee el elemento que está en la cima de la pila, pero sin desapilarlo. Hay un objeto prec
que nos da un valor numérico de la precedencia de cada operador. La expresión infija como 2*3+4
se divide en tokens que no es otra cosa que un Array con sus partes ["2", "*", "3", "+", "4"]
. Iteramos por los tokens, si es un operando lo metemos en rpn
. Si es un operador lo metemos en la pila
. Pero antes comprobamos si en la cima de la pila hay un operador con mayor precedencia, en cuyo caso éste se desapila y pasa a rpn
. La precedencia de operadores es en el orden / * + -
. En esta tabla puede ver como pasamos del infijo 2 * 3 + 4
al RPN 2 3 * 4 +
Paso | Token | RPN | Pila | Notas |
---|---|---|---|---|
1 | 2 | 2 | Operando 2 va a RPN | |
2 | * | 2 | * | Operador * va a la Pila |
3 | 3 | 2 3 | * | Operando 3 va a RPN |
4 | + | 2 3 * | + | Como * tiene precedencia sobre +, quitamos * de la Pila y lo pasamos a RPN y ponemos + en la Pila. |
5 | 4 | 2 3 * 4 | + | Operando 4 va a RPN (se acaban los tokens) |
6 | 2 3 * 4 + | Al final sacamos todo lo que hay en la Pila y lo ponemos en RPN |
Una vez que tengamos la expresión en RPN resulta muy sencillo calcularla, para lo cual también usaremos otra pila:
let pila = []; //En rpn tenemos un RPN como ["2", "3", "*", "4", "+"] for (let token of rpn){ if ("*/+-".indexOf(token)==-1){ // Operando pila.push(token); } else { // Operador let val1 = parseFloat(pila.pop()); let val2 = parseFloat(pila.pop()); let calculo = (token=="+") ? val2 + val1 : (token=="-") ? val2 - val1 : (token=="*") ? val2 * val1 : (token=="/") ? val2 / val1 : null; pila.push(calculo); } } //Al final la pila tiene un único elemento con el resultado: 10
Vamos apilando los operandos. Cuando lleguemos a un operador desapilamos los dos últimos operandos, realizamos el cálculo y lo apilamos. Este proceso seguirá hasta que en la pila sólo quede un único elemento con el resultado final.
En el ejemplo interactivo siguiente se incluyen paréntesis y el operador "^". No he contemplado el cálculo de funciones. Puede ver el código en el enlace con comentarios explicativos.
Ejemplo: Ejemplo de uso de una pila para convertir y ejecutar RPN
Tokens:
Resultado:
arr.unshift(elem1, elem2, ...)
Método para agregar al inicio del Array uno o más elementos. Devuelve la longitud actualizada.
let arr = [3, 4, 5]; arr.unshift(1, 2); console.log(arr); // [1, 2, 3, 4, 5]
Como los métodos anteriores, este unshift()
también puede generalizarse con array-like. En este caso se lo aplicamos a arguments
para anteponerle un primer valor:
function fun(){ console.log(arguments); // [2, 3] [].unshift.call(arguments, 1); console.log(arguments); // [1, 2, 3] } fun(2, 3);
arr.shift()
El método shift()
elimina el primer elemento de un Array devolviendo su valor:
let arr = ["a", "b", "c"]; console.log(arr.shift()); // "a" console.log(arr); // ["b", "c"]
Implementando una cola con push() y shift()
Una cola es una estructura de datos donde el primero en entrar será el primero en salir (FIFO, first in first out
). Los nuevos elementos entran o se encolan por el final de la cola. La única forma de sacar un elemento es hacerlo por el principio de la cola, lo que llamamos desencolar.
El ejemplo típico de uso de una cola es la cola de impresión de los sistemas operativos. Por lo tanto son estructuras adecuadas cuando esperamos entradas para luego ser procesadas. Pero como la velocidad de proceso podría ser diferente a la de llegada de entradas, éstas las acumulamos en una cola para luego procesarlas en el mismo orden de llegada. Ese es el objetivo de un búfer de datos que se implementa con una cola.
Usaríamos cola.push(item)
para agregar un nuevo elemento a la cola, e item = cola.shift()
para sacar un elemento de la cola. En el siguiente ejemplo implementaremos una cola para que vaya almacenando la recepción de archivos de imagen. Los códigos siguientes son abreviados para una mejor explicación. En el enlace del ejemplo podrá ver el código original.
Tenemos una función para solicitar una imagen. Cuando se recibe la encolamos. El archivo lo recibimos como un Blob. Esta función se llama desde el bucle que itera por el Array de rutas listaImg
:
function obtenerImagen(ruta){ let request = new XMLHttpRequest(); request.open("GET", ruta, true); request.responseType = 'blob'; request.onreadystatechange = function(){ if (request.readyState == 4) { if (request.status == 200){ //Encolamos blob cola.push(request.response); } } }; request.send(); } //Iniciamos una cola let cola = []; //Peticionamos todas las rutas for (let ruta of listaImg){ obtenerImagen(ruta + '?m=' + parseInt(Math.random()*10000, 10)); }
De forma asíncrona podemos ir desencolando imágenes. El Blob puede ser usado para crear una referencia de URL que nos sirva para adjudicar a la propiedad src
de un elemento imagen, que luego será agregado a la página.
if (cola.length){ //Cargamos el blob en una imagen y la agregamos al DOM let img = new Image(); img.addEventListener("load", () => { //Quitar esta referencia pues ya no hace falta window.URL.revokeObjectURL(img.src); }); //Desencolamos el blob para ponerlo al src de la imagen let blob = cola.shift(); img.src = window.URL.createObjectURL(blob); document.getElementById("imgs-cola").appendChild(img); } else { //la cola está vacía }
La utilidad de la cola se vislumbra cuando tenemos una conexión lenta. En los Developer Tools de algunos navegadores podemos simular las condiciones de red. Si usamos un conexión lenta vemos que se van descargando archivos (encolando) y, asíncronamente, los podemos ir presentando en la página con el botón para desencolar.
Ejemplo: Ejemplo de uso de una cola
Lista con las rutas almacenadas en un Array.
Haremos un petición con XHR a esas rutas, recibiéndose no necesariamente en el mismo orden. A medida que se vayan recibiendo las encolaremos en una cola usando push(ruta)
. La siguiente lista es una presentación de esa cola con indicación del orden de llegada y del tamaño del archivo recibido. A cada ruta le agregamos una marca para forzar a que el navegador no meta en caché la ruta, pues nos interesa tener diferentes tiempos de recepción:
Mientras haya rutas en la cola podemos irlas desencolando con shift()
:
arr.copyWithin(target[, start[, end]])
Se trata de copiar segmentos dentro del Array. Copiamos un segmento como los elementos en un rango de índices [start, end)
, no incluyendo el superior, y luegos los pegamos a partir de la posición target
. Los dos argumentos start
y end
son opcionales, tomando los valores por defecto cero y length
respectivamente.
En el esquema de la Figura vemos como el trozo de Array que está en las posiciones segunda (start
= 2) y tercera (end
= 4) es copiado a partir de la posición primera (target
= 1).
let arr = ["a", "b", "c", "d", "e"]; arr.copyWithin(1, 2, 4); console.log(arr); // ["a", "c", "d", "d", "e"]
Los tres argumentos pueden ser negativos, en cuyo caso se contarán desde el final. Si en el ejemplo anterior a cada argumento le restamos la longitud del Array tenemos (1-5, 2-5, 4-5) = (-4, -3, -1)
. Entonces arr.copyWithin(-4, -3, -1)
nos dará el mismo resultado que obtuvimos antes ["a", "c", "d", "d", "e"]
.
El método modifica el Array sobre el que se aplica. Pero también devuelve dicho Array, aunque realmente lo que devuelve es una referencia al Array. En este ejemplo verá que tenemos un primer Array que modificamos con arr1.copyWithin()
y lo devolvemos en arr2
. Las dos variables son iguales porque ambas apuntan al mismo Array. Vease que contienen lo mismo tras aplicar el método. Y que cambiar una posición en una de las variables afecta a la otra.
let arr1 = ["a", "b", "c"]; let arr2 = arr1.copyWithin(1); console.log(arr1 === arr2); // true console.log(arr1); // ["a", "a", "b"] console.log(arr2); // ["a", "a", "b"] arr1[0] = 999; console.log(arr1); // [999, "a", "b"] console.log(arr2); // [999, "a", "b"]
arr.fill(value[, start[, end]])
Rellena los elementos de un rango [start, end)
(sin incluir el superior) en un Array con un valor. Si no se especifica start
se tomará desde cero y si no se especifica end
se tomará la longitud del array
let arr = [1, 2, 3]; arr.fill("a"); console.log(arr); // ["a", "a", "a"] arr.fill("b", 1); console.log(arr); // ["a", "b", "b"] arr.fill("c", 2, 3); console.log(arr); // ["a", "b", "c"]
Podemos usar arr.fill(valor, i, i+1)
como equivalente a arr[i] = valor
para escribir un valor en el elemento i-ésimo. Aunque con la forma de corchetes si no existe el elemento lo creará, mientras que con fill
debe existir ese elemento, pues en otro caso el método no tendrá ningún efecto sobre el Array:
let arr = ["a", "b"]; console.log(arr); // ["a", "b"] //Con esta asignación se crea nuevo elemento //en la posición 2 arr[2] = "c"; console.log(arr); // ["a", "b", "c"] //Pero con fill() no podemos agregar elemento //en la posición 3 pues no existe. No cursa //error y no hace nada en el Array. arr.fill("d", 3, 4); console.log(arr); // ["a", "b", "c"]
Los argumentos start
y end
pueden ser negativos, entendiéndose que estamos contando desde el final del Array. De cualquier forma el método solo trabaja con valores positivos, para lo cual restará esos negativos a length
para obtener la posición. Los valores negativos son útiles cuando nos resulta más cómodo precisar la posición contando desde el final. En este código rellenamos con x
desde la posición segunda contando desde el final hasta el final.
let arr = [1, 2, 3, 4, 5]; arr.fill("x", -2); console.log(arr);
Este método modifica el propio Array devolviéndo una referencia a ese Array, como podemos comprobar con el siguiente código, donde arr1
y arr2
apuntan al mismo Array:
let arr1 = [1, 2]; let arr2 = arr1.fill(3); console.log(arr1 === arr2); // true arr1[0] = "a"; console.log(arr1, arr2); // ["a", 3] ["a", 3]
El método puede generalizarse a los Array-like (no a String) con call()
o apply()
:
let obj = {0:1, 1:2, length:2}; [].fill.call(obj, "x"); console.log(obj); // {0: "x", 1: "x", length: 2}
Para aplicarlo a un String podemos usar el operador de propagación que convierte el String en un Array, aplicar fill()
para modificar todos sus caracteres y luego volver a unir el Array con join()
y un separador vacío para obtener de nuevo un String:
let str = "abc"; let equis = [...str].fill("x").join(""); console.log(equis); // "xxx"
arr.reverse()
El método reverse()
cambia los elementos en posición inversa. El método modifica el propio Array devolviendo otra referencia al mismo. En el código siguiente invertimos arr1
y el resultado lo ponemos en arr2
. Se observa que ambos Array son el mismo, pues al modificar arr1
se refleja también en arr2
.
let arr1 = [1, 2, 3]; let arr2 = arr1.reverse(); console.log(arr2); // [3, 2, 1] console.log(arr1 === arr2); // true arr1[0] = "x"; console.log(arr2); // ["x", 2, 1]
El método puede generalizarse a Array-like (no String) con call()
:
let obj = {0: "a", 1: "b", length:2}; [].reverse.call(obj); console.log(obj); // {0: "b", 1: "a", length: 2}
Para aplicarlo a un String podemos usar el operador de propagación para convertir el String en un Array, aplicar el método reverse()
, y finalmente unir ese Array con join
y un separador vacío para obtener de nuevo un String:
let str = "abcdefg"; let rts = [...str].reverse().join(""); console.log(rts); // "gfedcba"
El método sort()
ordena por defecto de forma ascendente. Una forma sencilla para que lo haga descendente es aplicar este método reverse()
tras aplicar sort()
:
let arr = ["HTML", "PHP", "JavaScript", "CSS"]; arr.sort(); console.log(arr); // ["CSS", "HTML", "JavaScript", "PHP"] arr.reverse(); console.log(arr); // ["PHP", "JavaScript", "HTML", "CSS"]
arr.sort([comparador])
Ordena los elementos de un Array modificándolo y devolviendo una referencia a dicho Array. El argumento es una función para realizar la comparación. Si no se especifica el orden se hará ascendente por puntos de código de Unicode. Aquí ordenamos un Array de números. La devolución es una referencia al mismo Array, como se observa al modificar un elemento en arr1
que también se manifiesta en arr2
:
let arr1 = [6, 1, 8, 11, -2]; let arr2 = arr1.sort(); console.log(arr1); // [-2, 1, 11, 6, 8] console.log(arr1 === arr2); // true arr1[0] = "x"; console.log(arr2); // ["x", 1, 11, 6, 8]
La ordenación por defecto es alfanumérica por puntos de código Unicode, por lo que en el ejemplo anterior "11"
va despues de "1"
. Para ordenar números hemos de usar una función de comparación. En lo siguiente la función toma dos elementos del Array y devuelve su resta.
let arr = [6, 1, 8, 11, -2]; arr.sort((x, y) => x-y); console.log(arr); // [-2, 1, 6, 8, 11]
La función comparadora debe devolver un valor (cualquiera) positivo, negativo o cero. Si x-y > 0
es que x > y
. Si x-y < 0
es que x < y
. Si la resta es cero es que son iguales. La anterior es una ordenación ascendente. La hacemos descendente cambiando el orden de la resta:
let arr = [6, 1, 8, 11, -2]; arr.sort((x, y) => y-x); console.log(arr); // [11, 8, 6, 1, -2]
Para ordenar Strings usando la función comparadora podemos aprovechar el comportamiento de los operadores con cadenas. Por ejemplo, la expresión "z" > "y"
nos dará verdadero mientras que "z" < "y"
nos dará falso.
//Los String se pueden comparar console.log("def">"abc"); // true console.log("def">"ghi"); //false //La ordenación por defecto de Array con Strings: let arr = ["ghi", "abc", "def"]; arr.sort(); console.log(arr); // ["abc", "def", "ghi"] //Es equivalente esto usando una función comparadora arr = ["ghi", "abc", "def"]; arr.sort((x, y) => (x>y) ? 1 : (x<y) ? -1 : 0); console.log(arr); // ["abc", "def", "ghi"]
Pero tenemos que tener cuidado cuando mezclamos números y cadenas en el Array. Una comparación como String > Number
pasará primero por convertir el String en un número. Si no lo consigue esa comparación se convierte en NaN > Number
que siempre será falso. Para ordenar un Array con String y Number pasamos a String todos los elementos y obtenemos su código Unicode. Podemos devolver una expresión como en el ejemplo anterior (x>y) ? 1 : (x<y) ? -1 : 0
, pero como los códigos Unicode ya son números simplemente devolvemos la resta. En este ejemplo además ordenamos descendente restando y-x
:
//La ordenación por defecto en un Array con números y cadenas //es siempre ascendente: números primero, luego cadenas let arr = ["abc", 123, "abd", "xyz", "XYZ"]; arr.sort(); console.log(arr); // [123, "XYZ", "abc", "abd", "xyz"] //Para hacerla descendente podemos usar una función comparadora arr = ["abc", 123, "abd", "xyz", "XYZ"]; arr.sort((x, y) => y.toString().charCodeAt(0) - x.toString().charCodeAt(0)); console.log(arr); // ["xyz", "abc", "abd", "XYZ", 123]
Antes usamos str.charCodeAt()
para obtener el código Unicode, pero tal como explicamos en ese enlace, sólo funciona hasta códigos FFFF
. Para contemplar todo Unicode hay que usar codePointAt()
. En este ejemplo usamos un Array con un código Unicode por encima de FFFF (65535)
, como se observa usando map()
para observar los Unicode. No olvide que map()
no modifica arr
. El método sin función comparadora ordena ascendente por defecto, pero si a ese resultado le aplicamos el método reverse()
tendríamos el Array en orden descendente. O podemos usar la función comparadora aplicando codePointAt()
.
//La ordenación por defecto contempla todo el Unicode (con ES6). //En este ejemplo aplica orden Unicode ascendente, pero luego //aplicamos el método reverse() para darle la vuelta let arr = ["☎", "Ñ", "🔒", "x"]; console.log(arr.map(x => x.codePointAt(0))); // [9742, 209, 128274, 120] arr.sort(); // Ascendente console.log(arr); // ["x", "Ñ", "☎", "🔒"] arr.reverse(); // Invertimos y equivale a descendente console.log(arr); // ["🔒", "☎", "Ñ", "x"] //En todo caso podríamos hacer descendente con la función comparadora arr = ["☎", "Ñ", "🔒", "x"]; arr.sort((x, y) => y.toString().codePointAt(0) - x.toString().codePointAt(0)); console.log(arr); // ["🔒", "☎", "Ñ", "x"]
El método de String str.localeCompare()
nos permite realizar una comparación según el idioma. En ordenación por puntos Unicode el caracter ä
está después de z
. Así se ordena por defecto. Pero en alemán ä
se debe ordenar antes de z
.
let arr = ["ä", "z"]; console.log(arr.map(x => x.codePointAt(0))); // [228, 122] arr.sort(); console.log(arr); // ["z", "ä"] arr.sort((x, y) => x.localeCompare(y, "de")); // Alemán console.log(arr); // ["ä", "z"]
Si los elementos del Array son objetos, podemos aplicar la función comparadora para ordenar esos objetos por alguna de sus propiedades:
let arr = [{prop: "ghi"}, {prop: "abc"}, {prop: "def"}]; arr.sort((x, y) => (x.prop>y.prop) ? 1 : (x.prop<y.prop) ? -1 : 0); //obtenemos [{prop: "abc"}, {prop: "def"}, {prop: "ghi"}]
En la ordenación pueden entrar en juego todas las propiedades que queramos. En este ejemplo ordenamos primero por nombre y luego por apellido:
let personas = [ {nombre: "Peter", apellido: "Parker"}, {nombre: "John", apellido: "Doe"}, {nombre: "Peter", apellido: "Pan"}, {nombre: "Clark", apellido: "Kent"} ]; personas.sort((x, y) => { if (x.nombre > y.nombre){ return 1; } else if (x.nombre < y.nombre){ return -1; } else { if (x.apellido > y.apellido){ return 1; } else if (x.apellido < y.apellido){ return -1; } else { return 0; } } }); /* Finalmente obtenemos este ordenamiento: {nombre: "Clark", apellido: "Kent"}, {nombre: "John", apellido: "Doe"}, {nombre: "Peter", apellido: "Pan"}, {nombre: "Peter", apellido: "Parker"} */
Podemos aplicar el método sort()
a un String usando el operador de propagación para convertirlo en un Array. Tras la ordenación volvemos a reponer el String con el médodo join()
y un separador vacío:
let str = "gFac2DGe4Lmt1bA58B"; let strOrd = [...str].sort().join(""); console.log(strOrd); // "12458ABDFGLabcegmt"
Ordenando una tabla con el método sort()
Como ejemplo de aplicación del método sort
vamos a ordenar una tabla por columnas. Usaremos los títulos de las celdas <th>
para hacer click
sobre ellos y ordenar ascendente o descendente. Le tenemos que agregar un atributo data-sort
con los tipos "string"
, "number"
o "date"
, pues según el tipo de datos de la columna tendremos que gestionar la forma en que ordenamos esos datos. En el atributo title
irá inicialmente "ASC"
indicando que al pulsar ese título se ordenará ascendente.
<tr><th data-sort="string" title="ASC">Marca</th> <th data-sort="number" title="ASC">Precio</th> <th data-sort="date" title="ASC">Fecha</th></tr>
En JavaScript tenemos una primera función que extrae la tabla a un Array, pues en ese Array aplicaremos la ordenación. En primer lugar iteramos por todos los títulos de columnas (celdas TH) para ponerles un evento click
que ordene por esa columna. Necesitamos además otro Array con los nombres de los títulos, que obtenemos convirtiendo el array-like
de textos en celdas TH en un Array. Estos nombres se pasarán al evento para ordenar, pues allí nos servirán para reponer los datos ya ordenados de vuelta a la tabla. Por último convertimos el resto de filas en objetos, de tal forma que cada celda TD es una propiedad del objeto. El Array final que representa la tabla quedará así:
[{Marca: "Seat", Precio: "17100", Fecha: "15/03/2016"}, {Marca: "Renault", Precio: "12400", Fecha: "20/05/2016"}, ...... ]
Como vimos en los últimos ejemplos más arriba, los elementos de este Array son objetos con la misma estructura, por lo que podemos ordenarlos por alguna de sus propiedades. Esto equivale a ordenar por columnas en la tabla. Este es el código para extraer la tabla:
function extraerTabla(tabla){ let arr = [], nombres; let filas = tabla.getElementsByTagName("tr"); let titulos = filas[0].getElementsByTagName("th"); if (titulos){ for (let th of titulos){ th.addEventListener("click", () => ordenar(arr, nombres, th, tabla), false); } nombres = [].map.call(titulos, x => x.textContent); for (let i=1; i<filas.length; i++){ let cols = filas[i].childNodes; let obj = {}; for (let j=0; j<nombres.length; j++){ obj[nombres[j]] = cols[j].textContent } arr.push(obj); } } }
La función para ordenar es la siguiente.
function ordenar(arr, nombres, th, tabla){ let asc = (th.title.toLowerCase() === "asc"); let titulo = th.textContent; let dataSort = th.getAttribute("data-sort"); arr.sort((x, y) => { if (dataSort === "string"){ return (x[titulo] > y[titulo]) ? ((asc)?1:-1) : (x[titulo] < y[titulo]) ? ((asc)?-1:1) : 0; } else if (dataSort === "number"){ return (asc) ? x[titulo]-y[titulo] : y[titulo]-x[titulo]; } else { // date let arrDate = x[titulo].split("/"); let xDate = Date.UTC(arrDate[2], arrDate[1], arrDate[0]); arrDate = y[titulo].split("/"); let yDate = Date.UTC(arrDate[2], arrDate[1], arrDate[0]); return (asc) ? xDate - yDate : yDate - xDate; } }); let filas = tabla.getElementsByTagName("tr"); for (let i=1; i<filas.length; i++){ let cols = filas[i].childNodes; for (let j=0; j<cols.length; j++){ cols[j].textContent = arr[i-1][nombres[j]]; } } th.title = ((asc)?"desc":"asc").toUpperCase(); }
Obtenemos el título, el orden ascendente o descendente y el tipo de campo. Aplicamos el método sort()
al Array con los datos de la tabla, gestionando la ordenación según el tipo de campo. Si es "string"
comparamos x[titulo]
con y[titulo]
, tal como vimos en uno de los ejemplos para ordenar objetos usando una de sus propiedades. Si es un "number"
solo aplicamos una resta.
Si es "date"
hemos de convertir esos String con fechas en formato "dd/mm/aaaa"
en un tipo Date de JavaScript, que sabemos que es un número entero con milisegundos desde 01/01/1970 y, por tanto, podemos restarlos para ordenarlos. Usamos el método genérico Date.UTC(year, month, day)
para generar esas fechas.
Por último vertemos el contenido del Array en las celdas TD de la tabla, para lo que usaremos el Array de nombres de títulos, pues están dispuestos en ese Array en el mismo orden que las columnas de la tabla. Finalmente cambiamos "ASC/DESC"
en el title
del TH para que esté disponible en la próxima acción del usuario.
Ejemplo: Ejemplo de uso de sort() para ordenar una tabla
Tras iniciar el ejemplo, puede ordenar por columnas pulsando sobre el título de la columna.
Marca | Precio | Fecha |
---|---|---|
Seat | 17100 | 15/03/2016 |
Renault | 12400 | 20/05/2016 |
Citroen | 28850 | 31/12/2015 |
Mercedes | 44600 | 12/04/2016 |
Audi | 35999 | 01/02/2016 |
arr.splice(start[, num[, elem0[, elem1, ...]]])
El método arr.splice(start, num)
elimina el número de elementos indicado en num
empezando en la posición start
. Opcionalmente pueden insertarse en esa posición los elementos elem0, elem1, ...
. El método modifica el propio Array, devolviendo un Array con los elementos eliminados.
let arr = ["a", "b", "c", "d"]; let eliminados = arr.splice(1, 2); console.log(eliminados); // ["b", "c"] console.log(arr); // ["a", "d"]
Podemos insertar elementos en el hueco donde se eliminan. Aquí eliminamos dos e insertamos tres elementos:
let arr = ["a", "b", "c", "d"]; arr.splice(1, 2, 999, 888, 777); console.log(arr); // ["a", 999, 888, 777, "d"]
También podemos insertar elementos sin necesidad de eliminar, sino sólo insertando, para lo cual pondremos cero en el segundo argumento num
:
let arr = ["a", "b", "c", "d"]; arr.splice(1, 0, 999, 888, 777); console.log(arr); // ["a", 999, 888, 777, "b", "c", "d"]
Cuando tenemos una serie consecutiva de elementos podemos usar el operador de propagación de elementos de un Array. Este ejemplo propaga los elementos del Array numeros
como argumentos para splice()
:
let arr = ["a", "b", "c", "d"]; let numeros = [999, 888, 777]; arr.splice(1, 2, ...numeros) console.log(arr); // ["a", 999, 888, 777, "d"]
Si no especificamos el número de elementos, se vaciará el Array:
let arr = ["a", "b", "c", "d"]; let eliminados = arr.splice(0); console.log(eliminados); // ["a", "b", "c", "d"] console.log(arr); // []
Puede ver más detalles sobre este tema de eliminar elementos de un Array.