El constructor de Array en ES6
Usando el constructor Array()
El constructor de Array es una función que tiene algunos métodos genéricos (o estáticos) que será interesante exponerlos en este tema. Uno de ellos es Array.isArray(obj)
para comprobar si un objeto es un Array. El método Array.of()
es de ES6 y viene a suplir el problema que existe con el constructor Array()
, puesto que no nos permite crear un Array de un único elemento cuyo valor sea un entero no negativo.
El método Array.from()
es también de ES6 a raíz de la inclusión de los objetos iterables. Estos implementan el símbolo [Symbol.iterator]
. Desde un iterable podemos construir un Array con este método Array.from(iterable)
. Son iterables Array, String, Set, arguments y otros. Por otro lado un objeto que tenga claves numéricas correlativas empezando en cero y una propiedad length
con el total de elementos también puede ser usado en Array.from()
. Generalizamos a veces denominando a todos estos objetos como array-like, pues son como Array
y pueden ser usados en muchos métodos tanto genéricos como del prototipo de Array.
El resto de métodos estarán disponible en el prototipo y los veremos con detalle en los temas siguientes, exponiéndose en el último apartado de este tema una lista de ellos.
Empecemos el tema viendo el constructor Array()
que podemos usar para crear un nuevo Array en lugar de usar la notación literal. Los argumentos serán los elementos del Array. Observe como el método toString()
aplicado a la variable que contiene el Array nos da un String representando los elementos separados por comas.
let arr = new Array(1, 2, 3); console.log(arr[0]); // 1 console.log(arr[1]); // 2 console.log(arr[2]); // 3 console.log(arr.length); // 3 console.log(arr.toString()); // "1,2,3""
Un Array creado con notación literal como con el constructor Array()
responderán al operador instanceof
diciéndonos si es instancia de ese constructor.
let arr = new Array(1, 2, 3); // o literal [1, 2, 3] console.log(arr instanceof Array); // true
Con ES6 podemos crear una clase que herede de Array, de tal forma que las instancias de esa clase serán de Array y de la clase al mismo tiempo:
class MiArray extends Array {} let arr = new MiArray(1,2,3); console.log(arr); // [1, 2, 3] console.log(arr instanceof Array); // true console.log(arr instanceof MiArray); // true
No hay ninguna diferencia entre un Array creado con notación literal o mediante el constructor. Sólo debemos tener en cuenta una cosa. Si sólo hay un argumento y es un número entero no negativo, el constructor creará un Array con la propiedad length
igual ese argumento. Pero realmente no hay nada en el Array, como se observa en el siguiente código donde los índices 0 hasta 2, los tres de length
, nos devuelven el valor undefined. Cuando una variable no contiene un valor, éste será el valor undefined. Vea como el toString()
nos devuelve dos comas seguidas que separa tres valores vacíos, indicando que no hay nada en esas posiciones.
let arr = new Array(3); console.log(arr.length); // 3 console.log(arr[0]); // undefined console.log(arr[1]); // undefined console.log(arr[2]); // undefined console.log(arr.toString()); // ",,"
Si usa un número que no sea entero no negativo (el cero si es posible), nos dará un error de rango:
let arr = new Array(-1); // RangeError: Invalid array length let arr = new Array(3.5); // RangeError: Invalid array length
Por lo tanto no es posible crear un Array con un único elemento que sea un número usando el constructor. Pero usando un único valor de otro tipo se creará un Array con ese único elemento. De paso observe que también podemos omitir la palabra clave new
con estos constructores.
let arr = Array("a"); // También es posible omitir "new" console.log(arr); // ["a"]
ES6 introduce el método genérico Array.of()
, que hace lo mismo que el constructor Array()
, pero solventa el problema de crear un Array con un único elemento que sea un número entero.
Siempre se recomienda crear valores usando literales en lugar de los constructores. Veamos si el motivo pudiera también estar relacionado con la eficiencia de JavaScript en el proceso de creación. En el siguiente ejemplo ejecutamos un bucle durante un tiempo determinado llevando a cabo la operación de creación de Array literales y con el constructor. Hay dos opciones: con números enteros y con cadenas.
while (new Date() - ini < tiempo){ iteraciones++; if (literal) { let x = (numerico) ? [1, 2, 3] : ["a", "b", "c"]; } else { let x = (numerico) ? Array(1, 2, 3) : Array("a", "b", "c"); } }
Lo anterior es una parte del código que ejecuta el siguiente ejemplo. Puede ver el código completo en el enlace al pie del ejemplo.
Ejemplo: Eficiencia notación literal vs constructor de Array
Durante ese tiempo ejecutaremos un código para crear valores de Array literal
let x = ["a","b","c"]
y con el constructor
let x = Array("a","b","c")
Cuantas más iteraciones hagamos más eficiente será el proceso
Iteraciones (Cuanto mayor mejor) | % mejora literal s/ constructor | ||
---|---|---|---|
Literal | Constructor | Diferencia | |
En los navegadores actuales Chrome 51 y Firefox 46 no se observan diferencias importantes entre crear un Array literal o usando el constructor.
Propiedades genéricas de Array
En el siguiente ejemplo podrá consultar las propiedades genéricas de Array()
en el navegador que está usando.
Ejemplo: Propiedades genéricas de Array
en este navegadorEl ejemplo anterior en Chrome 51 nos devuelve lo siguiente:
arguments
: objectcaller
: objectfrom
: functionisArray
: functionlength
: numbername
: stringof
: functionprototype
: object
El constructor Array()
es una función. Las propiedades del constructor se les denomina genéricas o estáticas, para diferenciarlas de las que encontraremos en el prototipo. En las genéricas encontramos las propiedades típicas de una función: arguments
, caller
, length
y name
(puedes ver más en propiedades de las funciones).
En prototype
encontraremos los métodos del prototipo, que veremos en el siguiente apartado. También estará en el prototipo otra propiedad length
que no debemos confundir con la genérica. Ésta devuelve el número de parámetros de la función. El length
del prototipo nos dará el total de elementos de un Array.
//La propiedad length de una función nos devuelve //el número de parámetros que se declararon function fun(a, b){}; console.log(fun.length); // 2 //Para los constructores built-in nos devuelve 1 console.log(String.length); // 1 console.log(Number.length); // 1 console.log(Array.length); // 1 //Para una instancia de un Array nos devuelve //el número de elementos del Array let arr = [1, 2, 3]; console.log(arr.length); // 3
Array.isArray(obj)
Comprueba sin un objeto es un Array. En el siguiente código se detecta el Array pero no el objeto que parece un Array.
console.log(Array.isArray(["a", "b"])); // true console.log(Array.isArray({0: "a", 1: "b", length: 2})); // false
Saber el tipo de datos en JavaScript no siempre es fácil. Cuando son estructuras de datos como un Array, el valor de typeof
siempre nos devuelve "object"
. Podríamos usar el nombre del constructor, lo que devuelve el método toString()
o el operador instanceof
.
let arr = [1, 2, 3]; console.log(typeof arr); // "object" console.log(arr.constructor.name); // "Array" console.log(Object.prototype.toString.call(arr)); // "[object Array]" console.log(arr instanceof Array); // true
Llevarse por el nombre del constructor no siempre tendrá que funcionar. En este ejemplo usamos las nuevas clases con extends
que hace que un objeto herede de otro. Ahora se observa que el nombre del constructor no es "Array"
sino "MiArray"
:
class MiArray extends Array{} let arr = new MiArray(1,2,3); console.log(arr.constructor.name); // "MiArray"
Utilizar Object.prototype.toString.call()
no es del todo seguro, pues con los nuevos símbolos de ES6 ahora es posible modificar la cadena "[object Array]"
. En este ejemplo lo hacemos para que con cualquier Array generado obtengamos "[object ???]"
:
Array.prototype[Symbol.toStringTag] = "???"; let arr = [1, 2, 3]; console.log(Object.prototype.toString.call(arr)); // "[object ???]"
Por último con el operador instanceof
tendremos un problema cuando consultamos Array desde distintos espacios globales. En el navegador el espacio global se identifica con la ventana por medio del objeto window
. Cada espacio global tiene sus propias variables. Los objetos built-in como Array no son compartidos entre ventanas. Por lo tanto desde una ventana no podremos saber si un objeto de otra es o no un Array usando ese operador.
En el siguiente código creamos una nueva ventana w
y un Array w.arr
en ella usando su constructor w.Array()
. Vemos que w.arr
no es una instancia del constructor en la ventana actual, pero si lo es de la nueva ventana. El nuevo método isArray()
viene a solventar estos problemas, pues determina claramente que w.arr
es un Array.
let w = window.open("about:blank"); console.log(w === window); //false w.arr = new w.Array(1,2,3); //El instanceof se limita a cada ventana console.log(w.arr instanceof Array); // false console.log(w.arr instanceof w.Array); // true //Pero isArray es independiente de la ventana console.log(w.Array.isArray(w.arr)); // true console.log(Array.isArray(w.arr)); // true
Array.of(elem0, elem1, ...)
El constructor Array()
tiene un comportamiento extraño con los argumentos. Si hay más de un argumento creará un Array con tantos elementos como argumentos. Si hay un único argumento y no es un número entero no negativo, entonces también creará un Array con un elemento con ese valor. En otro caso tomará ese único argumento como longitud del Array que va a crear, con todos sus elementos sin valor.
Como no es posible crear un Array de un elemento con Array(N)
, ES6 introduce el método Array.of(N)
, pudiendo pasarse un único valor para crear un Array con un sólo elemento.
let arr1 = Array(3); console.log(arr1); [] console.log(arr1.length); // 3 let arr2 = Array.of(3); console.log(arr2); // [3] console.log(arr2.length); //1
Array.from(arrayLike[, callback[, thisArg]])
Con Array.from()
podemos crear un Array desde cualquier objeto iterable o que sea array-like
. Los iterables implementan el símbolo [Symbol.iterator]
. Los Array son obviamente iterables, pero también lo son otros objetos como String, Set y Arguments, tal como vemos en el siguiente código de ejemplo:
//Un conjunto es iterable let set = new Set().add(1).add(2).add(3); console.log(typeof set[Symbol.iterator]); // "function" console.log(Array.from(set)); // [1, 2, 3] //Un String es iterable let str = "abc"; console.log(typeof str[Symbol.iterator]); // "function" console.log(Array.from(str)); // ["a", "b", "c"] //Arguments es iterable function fun(){ console.log(typeof arguments[Symbol.iterator]); // "function" return Array.from(arguments); } console.log(fun(1,2,3)); // [1, 2, 3]
Los array-like
son objetos que tienen propiedades numéricas que pueden actuar como índices y una propiedad length
con el número total de elementos. En el siguiente ejemplo creamos un objeto de esa forma, observándose que no dispone de [Symbol.iterator]
, pero aún así al ser algo parecido a un Array
, el método from()
es capaz de usarlo:
//Un objeto puede ser "array-like" let obj = {0: "x", 1: "y", 2: "z", length: 3}; console.log(typeof obj[Symbol.iterator]); // "undefined" console.log(Array.from(obj)); // ["x", "y", "z"]
La propiedad length
en el objeto anterior es imprescindible para que el método from()
devuelva el Array. Si esa propiedad no existe devolverá una Array vacío. Pero aún sin la propiedad length
podemos hacer iterable cualquier objeto con índices numéricos si le agregamos una función generadora a su símbolo [Symbol.iterator]
:
//Un objeto puede ser iterable con una función generadora let obj = {0: "x", 1: "y", 2: "z", [Symbol.iterator]: function* () { let index = 0; while (this[index]) yield this[index++]; } }; console.log(typeof obj[Symbol.iterator]); // "function" console.log(Array.from(obj)); // ["x", "y", "z"]
Con destructuring podemos conseguir lo mismo que con este método from()
:
let str = "abc"; console.log([...str]); // ["a", "b", "c"]
Pero el desestructurado sólo se puede aplicar sobre objetos iterables. Al ejemplo del objeto array-like
no podemos aplicar esta técnica por que no tiene un método [Symbol.iterator]
:
let obj = {0: "x", 1: "y", 2: "z", length: 3}; console.log([...obj]); // obj[Symbol.iterator] is not a function
Este método Array.from(arrayLike[, callback[, thisArg]])
tiene los dos últimos argumentos opcionales que equivalen a aplicar el método map()
tras la creación del Array. Sería como hacer Array.from(iterable).map(callback, thisArg)
. La ventaja de usar los argumentos opcionales es que no creamos un Array intermedio. En el siguiente código creamos el Array a partir de las letras de un String, pero se devuelven el código ASCII de cada letra:
let str = "abc"; let arr = Array.from(str, v => v.charCodeAt(0)); console.log(arr); // [97, 98, 99]
El callback también se podría aplicar a un array-like. Como vimos más arriba, el objeto debe tener una propiedad length
. Podemos rellenar un Array dando una objeto con una determinada longitud:
console.log(Array.from({length: 3}, (v, i) => i)); // [0, 1, 2]
Ver más cosas sobre from()
y map()
.
Propiedades del prototipo de Array
En el ejemplo siguiente podrá consultar las propiedades del prototipo de Array en el navegador que esté usando.
Ejemplo: El prototipo de Array
en este navegadorEn Chrome 51 obtenemos 29 métodos del prototipo de Array, el constructor y length
, que ya vimos que es donde se almacena la longitud de un Array. Agruparemos los métodos según su finalidad para estudiarlos mejor. Cada apartado se presenta en un página distinta.
- 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.